ch03

【Thu Aug 18 2016 11:22:26 GMT+0800】

对象变量与对象之间是指代关系,对象变量并不能完全说明有无对象可用。这种指代关系是通过赋值运算建立起来的。对象变量保存的值是对象的指代关系,即就是对象在内存中的内存首地址。对象变量之间的赋值事实上只是赋值了指代关系(String 类型除外)!(因为对象变量只保存对象的内存空间地址,并不保存整个对象的内存空间)。Java中的对象的本质之一就是是指针(一块内存的首地址)。

null表示某一数组变量或类的对象变量指向一个不可用的内存数组空间或对象。被赋值为null的数组变量或类的对象变量具有不确定的指代关系。null 只适用于数组变量或类的对象变量,因为表示的是一种特殊的指代关系,核心落脚点是指代关系。所有具体的数组类型的变量与类类型的对象变量都可以被赋值为这种特殊的指代关系。被赋予了null 指代关系的对象或数组变量是不可以正常运行的!其实也可以认为Java 中的null 也是一个地址值为0 的特殊指针。

点操作符表示的是一种从属与包含的关系。

this关键字就是为了实例域与方法的局部变量或参数重名的问题。在方法中如果局部变量或参数与实例域的重名,这是被允许的,此时在方法体中直接使用这个变量名代表的不再是实例域,而是局部变量或参数。因为this 关键字的逻辑系,所以this关键字只能在类的定义体的实例方法中出现,一定是实例方法。

final,常变量:只读变量空间。

重载:同一类中的同名方法,参数个数或类型不同的方法构成“重载”。

构造方法通常是在类的定义体的外面,在使用类创建对象的时候使用。然而,Java 中允许在类的定义体内使用,但只能在另一个构造方法中使用,且必须是使用者构造方法的的第一条语句。通过this 关键字即可,形式是将this 当做函数使用:this(...);。

static 的真正的本质逻辑含义在于只此一份。不因创建对象而出现一个新的static 数据或方法,所有对象、类所看到的static 修饰的方法与类都是只有一个!实例域与实例方法每一个对象都有一个拷贝,而静态所修饰的域和方法全局只有一个拷贝。可以再来一个推论:既然如此,那么实例域和实例方法最终隶属于对象,而静态所修饰的域和方法只隶属于类。

静态块也有类似的功能,但并不是new 一个对象就会执行一次,而是整个类在第一次被使用的时候执行一次,之后就不会再被执行了。静态块的主要作用是用来初始化静态域的,因此又被成为静态初始化块。

静态方法全局只有一个,隶属于类。而实例方法在每一个对象中都有一个拷贝,隶属于对象。

静态方法不能使用本类对象的实例域和实例方法,只能访问本类的静态域和静态方法。这一点也适用于静态块。

【Fri Aug 19 2016 09:17:26 GMT+0800】

形参与实参的联系就是:实参的数据将会赋值给形参,那么在方法体中就能够通过形参使用实参的数据!其称之为参数的赋值传递。形参作为方法的一个局部变量,其保存的值来自于调用时给定的实参。

1)基础数据类型的变量保存的直接就是数据;2)数组类型与类类型的变量保存的是一个指代关系(除了String 类型)

因此在赋值时:1)基础数据类型变量的赋值时将变量的内存空间完全拷贝;2)数组类型与对象变量赋值时只是将指代关系完全拷贝(除了String 类型)

值传递、引用传递、指针传递。其实都是通过赋值运算在传递参数,只不过传递的内容分别是:具体的数据、引用、地址。

在Java中不存在指针的概念,也就不允许获得基础数据类型的变量的地址,也就不存在基础数据类型的指针传递。

参数传递的本质其实还是赋值运算。

数组的赋值事实上复制的是指代关系,即:赋值之后,让新的数组变量与原数组变量指向了同一个数组空间)。交换所指向的空间的值是可行的,但是不能交换指向关系。

其实,类类型的参数传递的本质:赋值的传递方式。类的对象变量的赋值,只是复制了对象的指代关系!但有一个类型除外,那就是String 字符串类。String 是一个类类型,但是它的赋值并不是复制对的指代关系,而是构建新的对象。

如果方法的参数是String类型的,那么形参并不会与实参指向同一个字符串对象,而是获得一个新的字符串对象,只不过是其值与实参的字符串对象中的值是一样的。那么在方法中对形参String对象做任何变化都对实参没有作用。

注意:如上描述的只是String 对象的这种赋值的逻辑过程,实际内存级别的物理存储过程并不是直接如此操作的。

ch05

this的本质含义是本类对象。

子类不能直接访问父类通过private修饰的内容。private修饰的内容不能被子类直接访问。

protected关键字的含义:受到包和继承关系保护的。

protected修饰的内容可以被属于同一个包下的其他类以及其子孙类访问。

某一个类型的对象不仅仅是属于该类型的对象,也从属于该类型的父类以及所有祖宗类的类型。

父类对象变量指向了子类对象空间。从另一个角度来看,那就是子类对象自动类型转换为了父类对象类型,并由父类对象变量保存。子类对象可以自动类型转换为父类,但对象空间并不会发生变化,变化的依然只是指代关系。

子类对象变量指向父类对象是可行的,但需要强制类型转换。

类类型的对象与对象变量之间的指向关系可以跨越父子类型的。从表面上来看具有类似于Java的8大基础数据类型之间的转换关系一样。但这并不能算作转换,因为实质性的对象内存空间并没有发生变化,只是指代关系发生变化而已。这被称之为“父子类型的兼容特性"!

父子类型的兼容性的前提是,相互兼容的两种类型必须是明确的父子或祖孙关系。如果不是祖孙关系或父子关系,而是其他的叔侄关系、兄弟关系等等都不可以。

例如:人类与哺乳类的对象变量与对象之间可以相互改变指代关系。但人类与啮齿类的对象变量与对象之间就不可以相互改变指代关系。

子类可以重新声明和定义从父类或祖先类继承得到的方法或者域,并不影响父类或祖先类的定义,这种机制就被称之为“覆盖”。

因为子类能够覆盖从父类继承得到的内容,那么在子类的定义体中,使用this能找到的不再是从父类继承得到的这些内容,而是在子类中覆盖得到的内容。

方法的覆盖
父类定义的public方法论是静态的还是实例的、无论是final的还是普通的,子类都能够继承,也都能够将其覆盖,但是覆盖是有一定的规则约束的:
1在子类中定义一个方法覆盖父类,这个方法的名称与参数必须与继承得到的方法完全一致。如果方法名称相同,而参数不同,则不是覆盖,但却是重载。(注:那么也就是说重载发生在一个类中或具有继承关系的多个类中。而覆盖一定是发生在两个有继承关系的类中)
2子类覆盖父类的方法时,新的方法可以重新定义访问权限,但是只能将访问权限的门槛降低,而不能增高。换句话说,覆盖方法时访问权限的修改原则是:包访问->protected->public,反向则不行,更不能改为private。
3被覆盖的方法若是实例方法,那么新方法也必须是实例方法。被覆盖的方法若是静态方法,那么新方法也必须是静态的。简单来说:静态方法与实例方法在覆盖时不能相互变迁。

一个类中使用final关键字修饰的方法,是不能被子类覆盖。只是不能被覆盖,但可以被重载(这并不矛盾,因为重载和覆盖事实上是两种完全不同的诉求,完全不同的逻辑出发点)。

在使用笼统的父类类型的对象变量指向子类对象的过程中产生的不确定性被称之为”多态“。一个父类的对象变量因为不确定到底会指向哪个子类对象,因此使用父类对象变量能够因为不同的指向关系而拥有不同的多个状态,称之为”多态“。

final修饰的类就是最终类,最终类是不允许被继承的,如果有类型要继承最终类,则被视为语法错误。

final修饰的数据,final修饰方法,final修饰类。

定义的任何一个类,若没有通过extends指明其继承于任何其他类,则该类会默认继承于Object类,Object类是当前类的父类。若使用了extends指明了其父类,并不表示类有两个父类,父类依然只有一个:通过extends指明的父类。

推论1:当使用extends指明当前类继承于某一个其他类型,则当前类是Object类的孙子类,Object类是当前类的祖先类,当前类还是会继承Object类中能够继承的内容。

这样就能够达到效果(如上推导的逻辑):无论怎样,Java中所有的类都直接或间接继承于Object类。

推论2:除了Object类以外,其他的任何一个类都有父类,其父类要么是Object,要么是其指定的其他类型。

推论3:所有类都在一个大的继承体系中,没有完全孤立的类。

Java程序设计概述

学习目标:
了解Java 概貌;
了解与Java相关的关键词,以便可以去了解其周边的知识;
能够安装Java开发环境
熟练使用命令行开发Java程序;

本章总结
本章的主要目的在于:1)给出Java 的概貌;2)为学习和实践Java 做准备;3)为此学习了一些基础概念和命令。
是否达到学习目标:
根据如下提示充分判断一下本章的学习效果。
目标1)是否完成了JDK 的下载与安装。
目标2)是否能够熟练使用Windows系统下的CMD 命令行下的基本命令。
目标3)是否完成了第一个Java程序实例。
目标4)是否理解并记忆了文件系统、目录、路径等基础概念,以及它们之间的关系。
目标5)是否了解了Java有关的关键词,并从中扩展阅读了一些其他资料。
目标6)是否了解了Java的基本背景知识。

Java 基本程序结构。

学习目标:
掌握最简单概念以及Java程序的基本结构以及注释写法;
掌握数据类型、类型转换、变量、运算符与表达式的概念及用法;
掌握输入、出程序的编写;
掌握控制流程的分支与循环的使用,并能够完成有一定复杂逻辑程序编写;
掌握数组的概念及使用,能够创建数组、遍历数组;
将本章所有示例都进行了上机实验。

本章总结
本章的主要讲述了很多程序设计的基本概念,另外也将Java的语法与这些程序设计通用基本概念结合讲解。这些内容几乎是所有“时序逻辑程设计语言”共同存在的几个方面: 1)数据类型与变量、常数 2)运算符与表达式; 3)流程控制 ;4)字符串; 5)数组; 6)输入与出。
本章的内容掌握之后就可以开始一些较小规模的程序的编写了,可以开始做更多的实验尝试。这些内容事实上是面向过程序设计的基础。本书并不回避一点,Java总体虽然是面向对象的,但无论怎样也法避开解决问题的步骤化过程。
如果读者没有其他任何编程语言的学习经验话,建议将本章的练习充分完成并尽可能多些耐心反复阅读几次,特别是对一些理解或记忆不很到位的内容。

是否达到学习目标:
目标1)是否已经熟练书写Java基本的程序结构(包括主方法的书写),并由此理解了标识符、 关键字、字符号、语句、程序块、函数与方法等等这些概念?
目标2)是否能够认识并书写注释?
目标3)是否理解了数据类型与变量的概念,并充分记忆了Java的8种基本数据类型的变量关键字、适用范围及特征?
目标4)是否理解了运算符的概念,并充分记忆和所有关键号与使用?是否将它们与数据类型进行了充分的实验?是否理解了数据类型的转换,并能够使用它们?
目标5)是否理解了字符串的概念,并能够创建字符串、拼接字符串?
目标6)是否能够熟练System.out.printf()与System.out.println()打印输入内容?
目标7)是否能够熟练地使用Scanner接收用户从键盘的输入信息?
目标8)是否充分理解了顺序、分支与循环的基本程序结构?对if、while、for、switch是否做了充分的练习?
目标9)是否对数组充分理解了,并对各种类型的各种维度的数组都做了相关的实验,并对照实验对书中讲述的内容进行了仔细思考?

类与对象

学习目标:
1)理解并掌握类、对象的概念,以及Java 相关的语法元素的使用,包括:class、new、构造方法。
2)理解并掌握实例域、实例方法的概念,以及相关的语法元素的使用,包括:this。
3)理解并掌握基于类的访问权限,以及Java 相关的语法元素的使用,包括:private、public。
4)理解并掌握静态的概念与static 关键字的使用。
5)理解并掌握常变量与final 关键字的使用。
6)理解并熟练掌握Java 类的定义及使用
7)理解并掌握包的概念。
8)理解并掌握重载的概念,并能编程使用。

本章讲述了两个重要内容:1)面向对象的思想核心;2)Java 语法对于面向对象思想的实现。因此拥有大量的概念以及Java 语法元素需要理解与记忆。
需要不断的熟练和深化面向对象的思想:
1) 一切都离不开类或对象,方法、数据。没有独立于任何类或对象指代的数据或方法。
2) 先考虑类与对象,再考虑他们之间的组织关系(类之间的关系:依赖、聚合、继承),最后考虑具体的每一个类的方法的实现过程。为了方便读者查阅,将本章面对的要关键字与概念做一总结与汇编。但注意,这只是为了方便初学者理解了本章内容之后的回顾与查阅,并不适合投机取巧的学习。

目标1)是否充分完成了本章中的所有示例与实验,并做了对照实验与推敲?
目标2)是否充分理解了面向对象的核心概念:类、对象、实例域、实例方法、继承关系、聚合关系、依赖关系?
目标3)是否能够将面向对象的核心概念与Java 中的语法元素之间建立起联系?
目标4)是否理解了静态的概念,并能解释Java 语法为何要求main 方法必须用static 修饰?
目标5)是否理解了基于类的访问权限?主观上能准确判断一个实例域或实例方法应该设计为哪种访问权限是合理的。
目标6)是否理解了包的概念以及作用,并能够熟练使用import 和package 关键字?

Eclipse

学习目标:
1)下载并安装Eclipse软件。
2)熟练使用Eclipse开发Java程。

本章总结
是否达到学习目标:
目标1)是否完成了本章的实验内容。
目标2)是否能够使用Eclipse熟练创建Java工程,是否理解了工程的概念。
目标3)是否能够使用Eclipse开始创建包、创建类,并能编译和运行程序。
目标4)将之前做的所有Java练习与实验都在Eclipse中练习至少一次。
目标5)是否能够调整Eclipse的界面布局。
目标6)是否能够调整Eclipse的字体。
目标7)是否充分实验过Eclipse的Debug功能,使用了Debug工作视图。
目标8)是否能够熟练配置库的源代码关联。
目标9)是否了解了Eclipse的工作空间目录下的内容。

类的继承

学习目标:
1)深刻解继承的概念及extends使用的基本语法形式;
2)能够深刻理解对象之间的类型转换的原理和方法,并能灵活运用;
3)能够深刻理解覆盖的概念,并区分重载与覆盖;
4)能够熟练使用final关键字;
5)对super关键字的含义充分理解和灵活使用。
6)理解并掌握多态的概念,并能尝试使用多态,或识别程序中的多态现象。

本章总结
继承的基本概念非常简单,一句话就能说清楚。但一个机制的出现往往要对很多有关的内容产生影响。所以要掌握继承,还得充分掌握因为继承而产生的诸多现象、机制与原理。这才是学习本章的最终目标。
是否达到学习目标
目标1)是否完成了本章的所有实验与所有示例(包括图画、草稿、伪代码)?
目标2)是否充分理解了继承的逻辑概念?
目标3)是否能够使用UML图表示类,并且表示类的继承关系(is-a、is-like-a)和组合关系(has-a)?
目标4)是否理解了Object类的重要性和特点(包括:hashCode、equals、toString方法的使用和覆盖)?
目标5)是否充分理解和记忆了final关键字的特性与使用方式?
目标6)是否能够灵活的使用super关键字与this关键字?

抽象类与接口

学习目标:
1)深刻理解与记忆抽象类的概念与语法,并能熟练运用;
2)深刻的理解接口的概念与语法,并能熟练运用。
本章总结
本章讲述了Java的语法机制中又一个非常强悍的部分,那就是使用抽象类与接口表示抽象概念。无论是extends还是implements其实都有继承的语义在其中。是否达到学习目标
目标1)是否完成了本章中所有的实验和示例?并理解了示例表达的含义?
目标2)是否理解了抽象类的概念及其语法?并能在具体使用是判断所要定义的类是否应该是抽象类?
目标3)是否理解了讲述抽象类时所描述的故事?
目标4)是否理解了接口所要表达的语义及其语法?

内部类。

学习目标:
1)深刻理解与记忆内部类的概念及语法特点;
2)深刻的理解内部类的来龙去脉,记忆和理解不同类型的内部类的特性;
3)能理解并初步感受到内部类存在的原因。
本章总结
内部类是一个看起来简单,但用起来却会有很多细节需要注意的Java机制。在内部类诞生的早期经历了很大的争论过程,因为很多工程师认为这种看起来花哨的语法机制不一定会有作用。但经过很多开发过程与工程师的努力,内部类在很多时候都展现出了其不可替代的能力。
因为一个类完全包容了另一个类,从而使得完全封闭的两个事物体系更好的融合与交流。再加之与抽象类和接口的灵活使用,还能组合出很多方便的设计模式,例如著名的迭代器设计模式(本章中也讲到了,但没有点明,读者可以查阅资料看看,Just a Game)。
只是一定要注意,内部类要使用得当,否则不仅不能简化设计,反而会使得整个设计变得复杂并且效率低下。这需要长期的软件研发实战,以及阅读足够多的软件设计类书籍。
是否达到学习目标
目标1)是否完成了本章中所有的实验和示例?并理解了示例表达的含义?
目标2)是否充分实验和理解了因为内部类而产生的this 和new的变化?
目标3)是否充分实验了不同类型的内部类?
目标4)是否充分了解了不同类型的内部类的特性?
目标5)是否充分实验了“为何需要内部类”?

异常

学习目标:
1)理解异常是一种存在于几乎所有语言的普遍的编程现象。
2)掌握Java中的异常捕获的概念以及语法。
3)掌握Java中的异常抛出的概念以及语法。
4)掌握Java中常用的异常类和异常的分类以及各自的特性。
5)掌握如何自定义异常。
无论是什么编程语言,无论是什么样的软件研发的实践都是存在异常的,也都存在可行的异常判别、捕获、弥补处理等一些列方式与机制的。这并不像某些说法认为的只有一些高级特性的语言才有异常。像Java这种高级语言,只是在语法层面提供了方便进行异常处理的简单机制。从而减轻了软件研发人员的压力,增加了软件研发的效率,也增加了软件的健壮性。
异常机制不难理解,语法其实也不难掌握,但在使用异常机制的时候:1)需要敏锐的发现和避免那些运行时异常,尽量不要使用try...catch去捕获运行时异常;2)需要准确的判断,一个异常是否应该捕获;3)需要准确的判断,一个异常是否应该抛出;4)需要准确的判断,一个异常是否应该先捕获,然后还需要抛出;5)如果需要定义属于自己的异常体系,则需要准确的判断怎样定义才算是合理的。
是否达到学习目标
目标1)是否完成了本章中所有的示例以及实验,并充分对比和思考了你所看到的所有现象。
目标2)是否能够自如的理解try...catch...finally的语法表示的逻辑?
目标3)是否能够自如的使用try...catch...finally的语法来捕获和处理异常?
目标4)是否能够自如的使用throws和throw语法来抛出异常?
目标5)是否能够理解异常类的分类事实上是一种异常语义的表达?
目标6)是否能够理解Throwable定义各个构造方法的含义?
目标7)是否能够理解调用堆栈的概念?是否能够看懂在控制台中打印的异常信息的调用堆栈的信息。

包装类型与自动箱

学习目标:
1)知道包装类型的概念。
2)理解并记忆自动装箱与自动拆箱的基本原理和使用方法。
3)理解并记忆各种包装类型的类名称、常用的方法与域。
掌握了Java中的类和对象的概念之后,学习包装类型以及自动装箱和拆箱事实上是非常简单的。但要记得包装类型的出发点是什么?它是为了提供一些便利方法,同时融合一些高级语法,例如:泛型1。
是否达到学习目标
目标1) 是否完成了本章中所有的示例以及实验,并充分对比和思考了你所看到的所有现象。
目标2) 是否知道有多少种包装类型。
目标3) 是否能够灵活的使用自动装箱与自动拆箱。
目标4) 是否使用过足够多的包装类提供的方法,并能很快自学一个新方法的使用。

学生信息管理系统

学习目标:
1) 了解软件工程的关键概念;
2) 学习一些关于数据结构的知识和技能;
3) 了解一些软件设计相关的知识和思维方式;
4) 深刻的练习Java语言。

本章总结
本章的主要目的是带领读者将已学到的Java技术运用于实际开发,但又不仅仅是为了练习学过的某一个单项技术点而练习。本章另一个主要目的则是通过讲述一个软件工程的开发过程来引导读者培养和训练软件研发的思维方式和习惯。
很多软件研发技术的初学者,一开始掌握了一点编程技术之后,在面对实际能够解决的编程问题时却不知道该思路在哪,如何下手。这主要是因为“完美主义”的思维习惯。很多软件开发技术的学习者都倒在了这个阶段,浅尝辄止,这其中也不乏那些一开始信誓旦旦要学好编程技术的学习者!解决方法就是:一来通过积极自我暗示的方法主动打破完美强迫症,并积极的动手编写代码;二来找一个可参考和学习的思路、思维、模式和方法。本章的另一个主要目的就是给出一个可参考的软件研发与编码的思维模式和方法。以便这些学习者能够逐渐自主独立的思考。

是否达到学习目标
目标1) 是否跟着本章讲述的内容完成了整个系统的开发?
目标2) 是否已经开始理解需求、需求分析、软件框架设计等等这些概念以及行为过程?
目标3) 是否对本章介绍的分层概念有了认识?
目标4) 是否对Java本身的语法特征有了更多的认识?
目标5) 是否对理解了链表这种数据结构,并能很好的编码完成它?
目标6) 对本章介绍的验证器接口的这种设计是否理解到位,并能顺利的编写类似的代码?

泛型。

学习目标:
1)理解泛型的作用和意义;
2)能够熟练使用泛型定义泛型类和泛型方法;
3)理解泛型的替换原则(类型擦除)与机制。
本章总结
泛型是Java中非常有用的一个机制,它使得Java程序更加灵活、可伸缩。初次接触可能并不是很好理解,要快速掌握,其要点就是要理解泛型的替换原则(类型擦除),其次就是要熟悉泛型的写法。
是否达到学习目标
目标1)是否完成了本章所有的示例与练习?
目标2)是否根据本章讲述做过逻辑推导?
目标3)是否理解了泛型的替换原则?
目标4)是否能熟练定义和使用泛型类?
目标5)是否能熟练定义和使用泛型方法?

虚拟存储空间
1、物理地址宽度<逻辑地址宽度,等价于在说:实际物力内存空间大小,小于OS所允许使用的逻辑空间大小;
2、编程时所使用的地址(包含变量名称、指针、&运算等等),也称逻辑地址;这样的逻辑地址是相对于本程序(编译成EXE文件)最开始位置的“偏移量”;这个逻辑地址,存在着被OS调入内存后再重新定位(确定切实的物理地址值)的过程。
当物理空间已被占满(空间已尽)时,若仍需执行某段程序(代码或者数据),则,可以将内存中当前(当下)尚未被执行的进程(包括指令或者数据),从内存中“移出”到外存中,腾出内存空间;这种技术叫“换入换出”;
这种技术可能产生的一种不良好的现象:由于“换出”的进程不得当,很快,它又要被调入内存,并反复如此,这将产生一种叫belady(抖动);使得计算机整体的工作效率拜急剧下降,最终将以外存速度为整个计算机系统效率的基础。

===========
4.5 super的使用  (kfjsdq P.197)

当子类的变量和方法与父类的同名时,父类的同名成员就被屏蔽了,而不是清除。关键字super可以在子类内访问父类成员及显示地调用父类的构造方法。super只能在子类内使用,用来调用父类的成员或构造方法。
4.5.1 用super引用父类的成员

无论是变量的隐藏还是方法的覆盖,都没有将父类的成员从内存中清除,只是让子类无法直接使用这些变量和方法。因此,关键字super不仅可以引用到父类中的隐藏的变量,还可以访问到被覆盖的父类方法。
4.5.2 使用super调用父类的构造方法

由于子类的构造方法会自动调用父类不带参数的构造方法,但是不会调用带参数的构造方法。因此,若子类确实有必要调用父类不带参数的构造方法,就必须使用super关键字来实现。

super使用时遵循的规则:

⑴关键字super只能用于构造方法中。

⑵关键字super只能第一条执行语句。

⑶一个构造方法只能有一条super语句。

一旦显示地用super来调用父类的构造方法,系统就不会再自动调用父类无带参数的构造方法。实际上,若不在子类的构造方法内写入super()或this()这样的语句,系统都会在第一行自动添加一条super()语句,实现对父类构造方法的调用。

4.6 继承的内部处理

当父类内的成员被子类继承后,并非将其复制了一份,放到了子类的内存空间中,父类内的成员仍然在父类空间中存在一份。

若程序通过“子类对象名.成员名”的方式使用成员,编译器会首先到子类内查找是否存在次成员。若没有,再到父类空间内查找,依此往上推,直到Object类还未发先此成员,则编译器报错。

若成员方法要访问成员变量,也是首先在本类内查找是否存在该成员变量。若没有,则到父类及其祖先类空间中查找,直到Object类为止。

因为父类的成员方法没有被复制到子类空间中,所以子类对象在运行时,必须保证父类的class文件可以被访问。否则运行时会报错。

为了保证父类和子类有不同的空间,系统在生成子类对象时,会自动生成一个父类隐藏的对象。若父类还有父类,则依此类推。所以,自动添加super(),正是由于实现父类对象的构造。
4.7 多态的基本概念

在OOP中,多态性不仅是指同一名词有多种语义,还与类密切相关,即:同一类的所有对象在收到同一条消息时,将采取同样的动作,而不同类的对象在收到同一条消息时,可能采取不同的动作。

Java的多态性,主要通过继承中的覆盖,和方法的重载来实现。继承中的覆盖是通过“动态绑定”技术实现运行时的多态。方法的重载使用“静态绑定”技术实现多态。
4.8 重载

Java允许在一个类内,多个方法拥有相同的名称。但是,名称相同的同时,必须有不同的参数,这就是重载。编译器会根据实际情况会挑选出正确的方法。若编译器找不到匹配的参数,或者找出多个可能的匹配,就会产生编译时错误,这个过程被成为“重载解析”。
4.8.1 普通方法的重载

普通方法的重载

⑴参数个数不同。

⑵对应位置上的参数类型不同。

当同名方法重载时,为了让编译器区分同名方法,这少有一条不同。

不允许参数完全相同,而只是返回值不同的情况出现。

静态方法重载规则与实例方法的重载一样。甚至,静态方法和实例方法之间也可以重载,同样要求参数之间有区别。
4.8.2 构造方法的重载

由于构造方法不能是static和final类型,而且也没有返回值。因此,构造方法的重载比普通方法简单一些。但一般的规则完全相同。

若为类定义了一个构造方法,则系统不会再自动为其添加无参数的构造方法。因此,若为类定义构造方法,为避免使用者的错误,务必定义一个不带参数的构造方法。
4.8.3 重载的解析

编译器确定调用哪一个构造方法的唯一依据是参数列表。这个确定的过程被成为“重载的解析”。

编译器解析顺序:

⑴根据调用的方法名,查找是否有定义好的同名方法。若没有,则会报错。

⑵比较形参和实参的数目是否相等。若没有,则会报错。若有一个或多个方法符合条件,则这些方法进入候选集。

⑶与候选集中的方法比较参数列表。若对应位置上的每一个参数类型完全匹配,或者可以通过扩展转换相匹配,则该方法称为“可行方法”。并进入可行集。若不存在可行方法,则会报错。

⑷在可行集中,按照下面的原则选取最佳可行方法。若最佳可行方法为0,则会报错。否则最佳可行方法就是最终确定要调用的方法。

选取原则:

⑴每一个参数都可以完全匹配的方法就是最佳可行方法。

⑵若某一方法的每一个参数匹配都不比别的方法差,且至少有一个参数比别的方法好,这个方法就是最佳方法。这里的“好”和“差”是指:完全匹配要比扩展转换”好“,扩展转换要比完全匹配”差“。然而,同样是扩展转换也存在“好”和“差”的问题。扩展转换有两条路径:

在这两条路径中,位于左边的类型都可以扩展转换成右边的类型。但是,源类型与目标类型的距离越近,则这种转换越”好“。例:byte转换成short,就比转换成int要”好“。int转换成float,就比转换成double要”好“。

show(int a, int b, int c);//①

show(int a, int b, double c);//②

show(int a, double b, double c);//③

show(double a, double b, double c);//④

show(1, 2, 3);//①②③④都是可行方法。所有参数完全匹配①,①是最佳可行方法。

show(1.0, 2.0, 3.0);//没有一个可行方法,错误。

show(1.0, 2, 3);//第2个参数可通过扩展转换匹配④,④是最佳可行方法。

show(1.0, 2.0, 3);//③④都是可行方法,没有最佳可行方法,错误。

show(1, 2, 3.0f);//②③都是可行方法。②的第2个参数比③更好,②是最佳可行方法。

编译器在重载解析时,只考虑对参数的匹配,不会考虑方法是否是静态还是实例类型。
4.8.4 重载与覆盖的区别

重载与覆盖都是多态的体现,它们的区别:

重载

参数列表不同

前面的修饰符有有限制

同一类内的方法,只能重载,不能覆盖

子类对父类的方法既可以重载也可以覆盖

重载时,编译器在编译期间就可以确定调用哪一个方法

-------

覆盖

参数列表完全相同

前面的修饰符没有限制

覆盖有可能在运行期间才能确定
4.9 运行时多态

运行时多态运行程序员不必在编制程序时,就确定调用哪一个方法,而是当方法被调用时,系统根据当时对象本身所属的类来确定调用哪个方法,这种技术成为“后期绑定”或“动态绑定”。由于这降低了程序的运行效率,因此只在子类对父类的方法进行覆盖时才使用。
4.9.1 实例方法的运行时多态

例4.24一个简单的覆盖

1 class forefather{
2 public void normal(){
3 System.out.println("这是父类的普通方法");
4 }
5 }

定义一个子类

1 class inheritor extends forefather{
2 public void normal() { //这里覆盖了父类的同名方法
3 System.out.println("这是子类的普通方法");
4 }
5 }

再定义一个用于测试的类

 1 class showSomething{
2 public static void main(String args[]){
3 forefather pfather; //定义一个父类变量
4 inheritor psun; //定义一个子类变量
5 pfather = new forefather(); //创建子类对象
6 pfather.normal(); //调用父类还是子类的方法
7 psun = new inheritor(); //创建子类对象
8 psun.normal(); //调用子类的方法
9 }
10 }

把这个类的第5行改写一下:

 1 class showSomething{
2 public static void main(String args[]){
3 forefather pfather; //定义一个父类变量
4 inheritor psun; //定义一个子类变量
5 pfather = new inheritor(); //创建子类对象
6 pfather.normal(); //调用父类还是子类的方法?
7 psun = new inheritor(); //创建子类对象
8 psun.normal(); //调用子类的方法
9 }
10 }

在这个程序中,pfather虽然定义成了一个类变量,但创建对象时,使用了子类的构造方法(第5行),这在Java中是允许的。Java还允许子类变量为父类变量赋值,如下示例:

pfather = psun;

反之,不能用父类变量为子类变量赋值。因此,在第6行调用normal方法时,就存在一个问题。由于pfather有2个含义:既是父类变量,又是一个子类对象。那么,pfather.normal()是调用了父类的还是子类中的normal()方法?Java处理这类问题采用了动态绑定的方式,即:在调用方法时,该变量是什么对象,就调用该对象所属的方法,与变量声明时所属类无关。因此,在pfather调用normal()时,是子类对象,所以调用的而是子类的方法。

对于此例,编译器在编译时就确定调用哪个方法,但在某些情况下,就未必如此,如下方法:

1 void showSomething(forefather p){
2 ...
3 p.normal();
4 }

调用时的实参既可以是 forefather对象,也可以是它的子类对象。而showSomething()方法中,是无法预先知道运行时的实际情况的,编译器更无法在编译时决定调用哪个类的方法。只有在运行到方法调用语句的时候,系统才能确定实际的对象。

4.9.2 成员变量运行时的表现

对于成员变量,无论是实例成员变量,还是静态成员变量,都没有实例方法的运行时多态特性。这是因为,成员变量引用在编译时就已经确定好了。

 1 class basePoint{
2 int x = 0, y = 0;
3 void move(int dx, int dy){
4 x += dx;
5 y += dy;
6 }
7 int getX() {
8 return x;
9 }
10 int getY() {
11 return y;
12 }
13 }

定义它的子类

 1 class realPoint extends basePoint{
2 float x = 0.0f, y = 0.0f; //隐藏父类的同名变量
3 void move(int dx, int dy){ //覆盖父类同名方法
4 move( (float)dx, (float)dy ); //这是调用子类的方法
5 }
6 void move(float dx, float dy){ //重载上面这个方法
7 x += dx;
8 y += dy;
9 }
10 int getX(){ //覆盖父类方法
11 return (int)Math.floor(x);
12 }
13 int getY(){ //覆盖父类方法
14 return (int)Math.floor(y);
15 }
16 }

在子类中,既有变量的隐藏,也有方法的覆盖,通过下面测试类,测试他们直接的区别

 1 class showDiff{
2 public static void main(String[] args){
3 realPoint rp = new realPoint(); //子类对象
4 basePoint p = rp; //p是父类变量,但指向了子类对象
5 rp.move(1.71828f, 4.14159f); //调用子类的move方法
6 p.move(1, -1); //调用子类的move方法
7 show(p.x, p.y); //显示的是父类对象的成员变量值
8 show(rp.x, rp.y); //显示子类对象的成员变量值
9 show(p.getX(), p.getY()); //调用子类的方法,获取子类对象的变量值
10 show(rp.getX(), rp.getY()); //调用子类的方法,获取子类对象的变量值
11 }
12 static void show(int x, int y){
13 System.out.println("(" + x + ", " + y + ")");
14 }
15 static void show(float x, float y){
16 System.out.println("(" + x + ", " + y + ")");
17 }
18 }

父类和子类内,分别有getX()和getY()方法,并各自返回各自类的中的x和y的值。

连续的rp.move()和p.move();调用的都是子类的方法,所以修改的是子类变量的值。

输出p.x和p.y的时候,虽然p实际上指向了一个子类对象。但是,p在声明的时候,是一个父类的变量。又因为,变量不具备运行时的多态特性,所以在编译时就会根据p的类型决定引用父类的变量。

成员变量与成员方法在多态上的区别也正是成员在被子类重写时,变量被成为“隐藏”,方法被成为“覆盖”的主要原因。

4.9.3 静态方法运行时的表现

静态方法与实例方法在运行时的多态表现不同的是,静态方法不具备运行时的多态的特性。

1 class Super{
2 static String greeting(){ //定义一个静态方法
3 return "晚上好";
4 }
5 String name(){ //定义一个实例方法
6 return "基类";
7 }
8 }

再定义一个子类,覆盖父类中的2个方法

1 class Sub extends Super{
2 static String greeting(){ //覆盖父类的静态方法
3 return "你好";
4 }
5 String name(){ //覆盖父类的实例方法
6 return "子类";
7 }
8 }

下面为这个程序区别

1 class differ{
2 public static void main(String[] args){
3 Super s = new Sub(); //定义父类变量,但是指向子类对象
4 System.out.println(s.greeting() + ", " + s.name());
5 }
6 }

这个测试程序,分别调用了greeting()方法和name()方法,而s声明了一个父类的变量,却指向了一个子类对象。

由于greeting()方法是一个静态方法,没有运行时的多态的特性。因此,greeting()方法的调用是在s声明时,就已经确定好了。

造成这种却别的原因是:实例方法总是在与某一个对象绑定在一起,而静态方法则没有与某一个对象绑定在一起。因此,也就无从查找调用时,该对象实际所属的类别。

4.10 抽象类与抽象方法

在类的继承结构中,越往上的类越具有通用性,也就越抽象。当类抽象到一定程度时,就变成了一个概念或框架,不能再产生实例化的对象了。例:无法用“交通工具”来产生一个实例。

对于这一现象,Java提供了抽象类。抽象类只能作为父类,不能实例化。定义抽象类的作用:将一对象的共同特点抽象出来,成为代表该类共同特征的抽象概念。其后,在描述某一具体对象时,只有添加与其它子类的不同之处,而无需重复类的共同特性。这样就使得程序概念层次分明,结构清晰,开发效率更高。

与抽象类紧密相连的是“抽象方法”。抽象方法总是用在抽象类或接口中。
4.10.1 抽象方法的声明

抽象方法是一种只有方法声明,而没有方法体定义的特殊方法。

声明抽象方法的几个限制:

⑴构造方法、静态方法和private方法都不能声明为abstract

⑵抽象方法只能出现在抽象类或接口中。
4.10.2 抽象类的定义

在抽象类中,可以有0个或多个抽象方法,也可以有普通方法的实例方法和静态方法,还可以其它的成员变量和构造方法。若类中没有任何形式的抽象方法,则可以有程序员自主决定将类声明成abstract类型。但是,只要有下面3种情况之一,则类必定为抽象类,必须加上abstract修饰。

⑴类内明确声明有abstract方法。

⑵类是从抽象类继承下来的,而且没有实现父类内全部抽象方法。

⑶类实现了一个接口,但没有将类内所有的抽象方法实现。

使用抽象类的唯一途径是派生一个子类。若所派生的子类实现了抽象类中所有的抽象方法,则所派生的子类就是一个普通的类,就可以用来创建对象。

若抽象类内有多个抽象方法,子类必须全部实现抽象类中的方法,才能用来创建对象。否则,子类也是一个抽象类。这个抽象的子类还可以被继承下去,由它的子类来实现其中那些剩余的抽象方法。
4.10.3 抽象方法与回调函数

在抽象类内,既可以有抽象方法,还可以有普通方法。甚至,普通方法还可以调用抽象方法。

1 public abstract class hasRecall{
2 abstract public void alert(); //这是一个抽象方法
3 public void doSomething(){ //这是普通方法
4 alert(); //调用抽象方法
5 }
6 }

由于doSomething()是一个实例方法,在被执行时创建了一个对象。而要创建对象,类必须是普通的实例化的类。这意味着,hasRecall这个抽象类内所有的抽象方法都已经被实现了,自然也包括alert()方法。因此,抽象方法不能是静态方法。这是因为,静态方法无需对象就能执行。

1 public class impRecall extends hasRecall{
2 public void alert(){
3 System.out.println("Warning!");
4 }
5 public static void main(String args[]){
6 hasRecall oa = new impRecall();
7 oa.doSomething();
8 }
9 }

Java之所以提供这种机制,是为了弥补Java没有类似于C/C++中的函数指针,无法实现回调函数这一缺陷。

所谓回调函数,是指函数f1调用函数f2,函数f2又调用函数f3,但是函数f3的函数体并不是确定的,函数f3是由函数1在调用函数f2时,作为参数传递给f2的,则f3被成为“回调函数”。由于回调函数的实现需要用到函数指针,而Java又是完全面向对象的语言,没有函数指针。因此,只能通过上述机制实现。
4.11 最终类与最终方法

若一个类不希望被其它类继承,则可以声明成final类。这样就防止了其它类以声明成final类作为父类。最终类通常是由某个固定作用的类,系统也提供这样的类:System、String和Socket类等。

由于上述可知,最终类不能有子类。因此,最终类不可能是抽象类。最终类所拥有所有的抽象方法都不可能被覆盖,因此最终类所有的方法都是最终方法。

最终类可以从其它类派生出来。由于最终类没有子类,因此声明成最终类的变量一定不会引用最终类子类的对象。因此,最终类的变量不存在运行时多态的问题。编译器可以在编译时确定每一个方法的调用,这样可以加快运行速度。

若一个类允许被其它类继承,只是其中的某些方法不允许被子类覆盖,则可以将这些方法声明成最终方法。

最终方法注意事项:

⑴最终方法可以出现在任何类中,但不能和abstract修饰符同时使用。

⑵最终方法不能被覆盖,但是可以被重载。
4.12 接口与多重继承

Java不允许类直接实现多重继承,而是用接口来实现。接口是Java中用来实现多重继承的一种结构。接口无论是从组织形式还是使用角度,都可以看成特殊的抽象类。
4.12.1 接口的定义

【例4.31】简单的接口定义

1 interface BaseColors {
2 int RED = 1, GREEN = 2, BLUE = 4; //这里都是静态公共常量
3 int getColorValue(int color); //这是一个抽象的公共方法
4 }

因为接口都是抽象的,所以不能用接口所定义的变量进行初始化。由于接口内的成员属性都是静态常量,因此可以用“接口名.变量名”的形式直接使用。

4.12.2 接口的继承

接口继承的规则与类的继承是相同的,而接口内的成员修饰符比类内的成员修饰符限制要多,因此也就比类内的简单。所以继承时的情况也就简单。

【例4.32】接口继承示例

1 interface RainbowColors extends BaseColors { //以BaseColors为父接口
2 int YELLOW = 3, ORANGE = 5, INDIGO = 6, VIOLET = 7; //新增加了4个成员常量
3 }

这个接口自动继承了父接口的3个成员常量和1个方法。

1 interface PrintColors extends BaseColors {
2 int YELLOW = 8, CYAN = 16, MAGENTA = 32;
3 int getColorValue(int color); //这里覆盖了父接口的成员方法,但它仍然是抽象的
4 int getColorValue(); //还可以重载
5 }

因为getColorValue(int)方法在PrintColors接口内仍然没有定义。所以,虽然PrintColors接口覆盖了父接口内的方法是没有语法错误,但实际上是多此一举。当类实现PrintColors接口或RainbowColors接口时,都只需要实现一个getColorValue(int)方法就可以了。

接口最重要的作用是实现多重继承,这就可能会产生歧义。

【例4.32】接口多重继承示例

1 interface LotsOfColors extends RainbowColors, PrintColors {//这是多重继承
2 int FUCHSIA = 17, VERMILION = 43, CHARTREUSE = RED+90;
3 }

这个LotsOfColors接口同时以RainbowColors和PrintColors为父接口。因此,LotsOfColors接口所有父接口内所有成员方法。

在BaseColors接口内,有3个属性:RED、GREEN和BLUE,分别被RainbowColors接口和PrintColors接口所继承。而LotsOfColors接口在继承RainbowColors接口和PrintColors接口这两个接口时,LotsOfColors接口、RainbowColors接口和PrintColors接口这3个从同一祖先继承下来的属性会合并成一份。因此,LotsOfColors接口只会继承10个属性。

同样,虽然BaseColors接口内的getColorValue()方法被两个接口所继承或是覆盖,但是继承到LotsOfColors接口内之后,也只有唯一的一份。因此,LotsOfColors接口内只继承了2个方法。

在RainbowColors接口和PrintColors接口有一个同名的常量:YELLOW。只是它们的值不同。由于YELLOW并非从一个祖先那里继承下来的。因此,在LotsOfColors接口内,会有两个同名的YELLOW属性。这在Java中是允许的,因此LotsOfColors接口在编译时并不会报错。但在使用LotsOfColors接口时,无法直接使用YELLOW属性。为了让编译器能够区分使用的是哪一个YELLOW属性,需要在YELLOW属性前面加一个接口名作为前缀。(接口内成员属性都是静态常量,因此可以使用这种方式)

RainbowColors.YELLOW || PrintColors.YELLOW;

在多重继承中,除了成员属性同名问题,还有成员方法同名的问题,如下将RainbowColors增加一个方法,与PrintColors类内的同名方法:

1 interface RainbowColors extends BaseColors {
2 int YELLOW = 3, ORANGE = 5, INDIGO = 6, VIOLET = 7;
3 int getColorValue(); //新增一个与PrintColors类内的同名方法
4 }

其它类不做修改。这样在LotsOfColors进行继承时,就会发现两个父类内存在两个名称、参数和返回值都完全相同的同名getColorValue()方法。对于两个同名getColorValue()方法,Java会自动将其合并成一个方法,后面的实现者只要实现一个就够了。

但若将RainbowColors改为

1 interface RainbowColors extends BaseColors {
2 int YELLOW = 3, ORANGE = 5, INDIGO = 6, VIOLET = 7;
3 void getColorValue(); //注意getColorValue()方法的返回值
4 }

此时与PrintColors内的getColorValue()方法的名称和参数都相同,但返回值不同。这既不是重载,也不是覆盖。编译器就无法明确让LotsOfColors继承哪个一了。此时,编译器在编译时,会报错。

4.12.3 接口的实现

接口最终要用类来实现。类对接口的继承被成为“接口的实现”。类在实现接口时,用implements关键字,而不再使用extends关键字。

若干没有继承关系的类可以是实现同一个接口,一个类也可以实现多个接口,这些接口都成为“父接口”或“超接口”。由于接口内只有抽象方法,因此一个非抽象的类必须实现父接口内所有的方法。若父接口继承了其它的接口,则这些由父接口继承的接口的抽象方法也要有该类实现。若是该类同时是某些类的子类,而其父类实现了这些接口内的一部分方法,则该类只要能继承这些方法,也就视为对这些抽象方法的实现。

因为抽象类可以将实现抽象方法的任务交由抽象类的子类来完成,所以抽象方法不受此限制。

1 [类修饰符] class 类名 [extends 父类名] [implements 接口名1 [,implements 接口名2...]]{
2 public [返回值类型] 方法名([参数列表]){
3 //方法体
4 }
5 }

实现接口内的方法的访问权限一定是public类型的。

虽然接口不能直接创建对象,但是接口内定义的成员变量是可以直接使用的。因为静态成员无需创建对象就可以直接通过“接口名.变量名”的方式使用。

1 void example(Colorable e){
2 e.getColor();
3 }

其中,Colorable是前面定义的接口,这个方法可以通过编译。当调用example()方法时,必定要提供一个参数,而这个参数肯定是一个实现了Colorable接口的子类对象。此时,getColor()方法已经被实现了。

4.13 内部类

内部类是是可以定义在另外一个类的里面。
4.13.1 内部类的定义

内部类分为3种:嵌入类(nested)、内部成员类(inner)和本地类。当类的前面有static修饰符时,此时就是“嵌入类”。嵌入类只能与外部成员并列,不能定义在方法内。若类与外部类的成员是并列定义的,且没有static修饰,则该类称为“内部成员类”。若类是定义在某个方法内,则该类成为“本地类”。

1.嵌入类的定义当内部类的前面加上static修饰符时,它就是一个嵌入类。它与外部类的其它成员属性和方法处于同一层次上。

[访问权限修饰符] static class 类名 [extends 父类名] [implements 接口列表]{
类体
}

嵌入类不能包含它的外部类名,也不能与其它的成员同名。

【例4.35】嵌入类的示例

1 class HasStatic{ //一个顶层类
2 static int sj = 100;
3 }

嵌入类与顶层类的定义几乎没有任何区别。但是,由于嵌入类本身是外部类的成员,因此嵌入类可以具有一般成员的访问权限。而顶层类只能是public或默认。

 1 class Outer_1{  //这是外部类
2 //下面是一个嵌入类,它可以继承其它的类
3 protected static class NestedButNotInner extends HasStatic{
4 static final int sc = 2; //正确,嵌入类可以有静态常量
5 protected static int si = 1; //正确,可以有静态成员变量
6 private int vi = 3; //正确,可以有实例成员变量
7 public void doSomething(){ } //正确,可以有实例成员方法
8 static void stShow(){ } //正确,可以有静态方法
9 }
10 }

编译这个类可以得到Outer_1.class和NestedButNotInner.class这两个class文件。NestedButNotInner.class就是内部类所生成的class文件。

2.内部成员类定义

若内部类前面不用static修饰,则它就是一个内部成员类。内部成员类的地位与类的实例成员相当,因此也称为“内部实例成员”。

[访问权限修饰符] class 类名 [extends 父类名] [implements 接口列表]{
类体
}

内部成员类与嵌入类的区别是:内部成员类的类体内,不允许存在静态成员(包括:静态成员变量和静态成员方法),但可以定义静态的常量。实例成员是允许定义的。

【例4.35】内部成员类的示例

 1 class Outer_2{  //这是外部类
2 //下面是一个内部成员类,它可以继承其它的类
3 private class Inner extends HasStatic{
4 static final int sc = 2; //正确,可以有静态常量
5 protected static int si = 1; //错误,不能有静态成员变量
6 private int vi = 3; //正确,可以有实例成员变量
7 public void doSomething(){ } //正确,可以有实例成员方法
8 static void stShow(){ } //错误,不能有静态方法
9 }
10 public void show(){
11 NestedButNotInner oa = new NestedButNotInner();
12 System.out.println(oa.sj);
13 }
14 public static void main(String args[]){
15 Outer_2 oa = new Outer_2();
16 oa.show();
17 }
18 }

内部成员类Inner的父类HasStatic是拥有静态成员变量的。内部类成员类尽管不能定义静态成员,但可以继承父类的静态成员。

3.本地类定义

内部类可以定义在方法内,此时的内部类成为“本地类”。无论方法体本身是静态方法还是实例方法,本地类都不能用static来修饰。本地类的类体内与内部成员类一样,只允许定义静态成员常量,而不允许定义任何静态成员。与嵌入类和内部成员类不同的是,本地类的作用域是定义本地类的方法,因此本地类没有访问类型。本地类的地位相当于定义了一个局部数据类型。

本地类不可以可以定义在一个方法内,甚至还可以定义在语句块内,则本地类的有效范围仅限于语句块。本地类不能定义静态成员,可以通过继承来拥有静态成员。

不同方法内的本地类是可以同名,甚至可以与嵌入类和内部成员类同名。但不要这样做!

【例4.35】本地类的示例

 1 class Outer_3{  //这是外部类
2 public static void StLocal(){ //静态方法
3 //下面是一个本地类,它可以继承其它的类
4 class Localize extends HasStatic{
5 static final int sc = 2; //正确,可以有静态常量
6 private int vi = 3; //正确,可以有实例成员变量
7 public void doSomething(){ } //正确,可以有实例成员方法
8 }
9 }
10 public void inLocal(){ //实例方法
11 //这是一个本地类,可以继承其它的类,这个类的名称和上面那个类相同
12 class Localize extends HasStatic{
13 static final int sc = 2; //正确,可以有静态常量
14 private int vi = 3; //正确,可以有实例成员变量
15 public void doSomething(){ } //正确,可以有实例成员方法
16 }
17 }
18 }

4.13.2 内部类访问外部类的成员

Java允许内部类访问包含它的外部类成员。此时,内部类相当于是外部类的一个普通成员。因此,内部类访问其它成员时,不受访问权限修饰符的限制。但内部类仍然受到static静态修饰符的限制。

1.嵌入类访问外部类成员

由于嵌入类本身是用static修饰的,因此编译器认为嵌入类内所有成员都处于静态环境中。所以嵌入类只能访问外部的静态成员,而不允许访问实例成员。

嵌入类是静态的,定义静态成员,使用外部类静态成员给它赋值。

2.内部成员类访问外部类成员

在内部成员类内,不能定义静态成员。但内部成员类的实例方法可以访问外部类的静态和实例成员。内部成员类内的实例方法在访问外部成员时,不受任何限制。

3.实例方法内的本地类访问外部类成员

由于包含本地类的方法既可能是静态方法,也可能是实例方法。这会导致本地类访问外部类时有所不同。

若本地类位于实例方法内,则本地类像内部成员一样,可以访问外部类的任意成员。包含本地类的方法可以定义局部变量,但是本地类只能访问局部常量,而不允许访问局部变量。

4.静态方法内的本地类访问外部类成员

若本地类位于静态方法内,则本地类处于静态环境中,像静态的嵌入类一样,只能访问外部类的静态变量。本地类只能访问局部常量,而不允许访问局部变量。

上表是内部类访问外部类成员时,所拥有的访问权限。由于内部类是外部类的成员,因此访问其它类成员时,并不受访问权限修饰符的限制。本地类只能访问包含自己的方法所定义的局部常量。

4.13.3 内部类之间的相互使用

由于嵌入类是静态的,因此嵌入类只能使用其它嵌入类,而不允许使用内部成员类。内部成员类可以使用嵌入类和其它内部成员类。由于本地类的作用域只限于定义它的方法,因此嵌入类和内部成员类都不能使用本地类。本地类可以随意使用嵌入类,但在使用内部成员类时,受到方法本身类型的限制。只用定义在实例方法内的本地类,才能使用内部成员类。同一方法内的本地类可以相互使用。

4.13.4 在外部使用内部类

对于只要访问权限不是private的嵌入类和内部成员类,就可以子外部使用,只是使用方式有所不同。本地类在外部无法使用。

若是嵌入类,可以像使用静态成员一样,通过“外部类名.嵌入类名”的方式使用。由于内部成员类是非静态的,因此必须通过外部类的实例进行引用。
4.13.5 匿名内部类

程序定义一个内部类之后,只要创建这个类的一个对象,就不必为这个类命名,这种类被称为“内部匿名类”。

new interfaceName(){
类体
}
new superClassName([实际参数列表]){
类体
}

匿名类是没有名称的。interfaceName和superClassName并非是匿名类的名称,而是匿名类要继承的接口或类的名称。括号中的参数是用来传递给父类的构造方法。

由于构造方法必须与类名相同,匿名类没有名称。因此匿名类没有构造方法,取而代之的是将参数传递给父类的构造方法。若是接口,则不能有任何参数。

使用一个内部匿名类的形式如下:

superClassName oa = new superClassName(参数]){ 实体 };

从形式上看,内部匿名类与一般创建对象的形式相似:

superClassName oa = new superClassName(参数]);

唯一区别是:内部匿名类后面有大括号括起来的类体。

由于匿名类在创建的同时,必须要创建对象。因此不能用static修饰。若匿名类与类的其它成员并列,则匿名类与内部成员类的定义没有区别。若匿名类写在一个成员方法内,则与本地类的规则相同。

若需将由匿名类创建的对象作为实参传递给某个方法。此时,对象也可以没有名称,形式如下:

function{new superClassName(){ 实体 };};

所有的匿名内部类都不是必需的。内部匿名类一定可以被其它类所代替。

4.13.6 内部类的作用

Java提供内部类的机制是为了实现多重继承。Java是利用接口实现多重继承的。例如系统提供了若干个接口,每个接口都声明了若干个方法。由于这些接口经常被使用,若要实现接口中的所有方法就显得特别麻烦,因此又为每个接口提供了实现的子类。只要继承某些子类并适当修改其中的部分方法就可以使用接口了。

若某个类通常需要继承多个接口才能运行,而类是无法实现多重继承的。这样,系统提供的子类就失去了作用。一种变通的办法是,将这些接口的子类,按照一定的顺序,形成一个继承链。这样,所定义的类只要继承最后的类实现了所有的接口。但问题是,大多数情况下,自定义的类只需要实现这些接口中的一部分。而这个继承链的顺序一定义好,就无法更改。除了继承这个链中最后一个必须是类外,没有其它更好的选择。在这种情况下,多数继承下来的类以及内部的方法都是毫无用处的,白白增加了编译运行的时间。

这么做的另外一个问题是,若两个不同接口内声明了两个相同的方法,则位于继承链后面的类,会将前面类实现的同名方法覆盖掉。这是个非常严重的问题,几乎无法解决。除非事先规定所有接口内的方法不得同名。

而内部类就可以解决上述问题,即:在自定义类中,可以增加若干个内部类,每一个内部类可以继承一个父类,并做必要的修改。选择哪个父类是任意的,无需增加不必要的父类。Java通过内部类加上接口,可以很好地实现多重继承的效果。特别是在GUI程序设计时,可以通过定义多个内部类来分别继承各事件的适配器类,解决多事件的处理问题。而不必为每个接口写实现代码。

4.14 包

包是一组由类和接口组成的集合。包的引入体现了封装性,包将类与接口封装在一个包内,每个包可能有若干个类和接口。同一个包不允许有同名的类和接口。包的引入,解决了类名的冲突问题。

包提供了一种命名机制和可见性控制机制(protected和默认访问属性),起到了既可以划分类名空间,又可以控制类之间访问的作用。由于同一包的类默认可以相互访问,因此将具有相似功能和具有共用性质的类放在同一包中。包可以实现不同程序间类的复用。

包本身是可以分级的,包内还可以有子包。

4.14.1 包的创建

可以定义自己需要的包。Java有两种包:命名包和未命名包。

未命名包的包都是处在顶层的包。在同一源文件中。所有类默认都属于同一个包。若类不在同一源文件中,但都是未命名的包,且处于同一工作目录下,则也认为是属于同一个包。

命名包的创建只要在Java源文件第一行写上package语句就可以完成,格式如下:

package 包名;

指定包名后,该源文件中所有的类都在这个包中。由于Windows中的Java是用文件系统来存放包的,因此必须要有一个和包名相同的文件夹,该包中所有的类编译生成的class文件都必须存放这个文件夹中才能正常使用。

1.用编译未命名包相同的方法

假设inPack.java位于e:\jdk1.5\example下

e:\jdk1.5\example>javac inPack.java

由于编译产生inPack.class类文件,是在当前目录下,而不是包名所对应的文件夹下,因此就不能想使用未命名包中的类那样直接使用java命令装载inPack.class文件来运行。而应该依次输入下列命令

e:\jdk1.5\example>md onePackage
e:\jdk1.5\example>move inPack.class onePackage

先建立与包同名的文件夹,再将包中的类文件复制到该文件夹下。

2.用编译未命名包相同的方法

e:\jdk1.5\example>javac -d . inPack.java

参数-d后面需要有一个空格,再加上一个“.”。

它会在当前目录下,查找是否有一个以包名为名称的文件夹(这里是onePackage)。若没有,则建立onePackage文件夹,然后自动将生成的class文件存到onePackage文件夹下。

由于关键字package必须是第一行,因此一个类不可能同时属于两个包。但两个不同的包中,可以有同名的类。

4.14.2 包的使用

包是类和接口的组织者,目的是为了更好地使用包中的类和接口。一个类只能引用本包中的其它的类。若要引入其它包中的类,则要使用Java提供的访问机制。

1.在引用类或接口名前加上所在的包名

其实是以包名作为类名的前缀。这与以类名作为成员的前缀类似。

2.是用关键字import引入指定的类

Java提供关键字import,用于引入包中的某个类或接口。形式如下:

import 包名.类名;

若有多个类需要引入,可以重复使用import。

3.是用关键字import引入所有的类

import 包名.*;

使用通配符(*)时,有一个限制。若被引入的包是系统预定义好的包,则无论什么情况下都可以使用。若自定义的包,则使用者也必须位于一个命名空间。

4.14.3 JAR文件的创建和使用

在一个包内会有很多.class文件。为了便于对这些文件的管理,以及传输这些文件所需的时间,Java允许把所有的类文件打包成一个文件,这就是“JAR文件”。JAR文件是压缩的,其压缩格式是ZIP。JAR文件不但可以包含.class文件,还可以像ZIP文件一样包含其它类型的文件。

创建JAR文件的命令

清单(manifest)文件是一个文本文件,它默认以.MF为扩展名,读者可以任意更改这个扩展名,它的主要文件名也可以任意指定。Jar命令在创建JAR存档文件时,如果指定了-m选项,则可以从清单文件中提取一些关于存档文件的附加信息。如指定存档文件中的主类(拥有main方法的类)。清单文件是一个ASCII 码 文本文件,它必须以一个空行作为结尾。
一个完整的清单文件可以包含很多条目,读者可以参阅API 手册,这里只介绍一些简单的规则。
一个清单文件中的条目被分成多个节。第一部分分为主节,它作用于整个JAR文件。后续条目用来指定已命名条目的属性,这些已命名条目可以是某个文件、包或URL。它们必须以名为Name 的条目开头,节与节之间用空行分开。

4.14.4 JDK中的常用包
4.15 本章小结

Java面向对象有2种特性:继承和多态。继承包括单一继承和多重继承。Java实现多重继承时,采用了接口和内部类相结合的方式,规则简单而且灵活。Java多态也分2种:静态的多态,是通过重载来实现。运行时多态,是指实例成员方法被覆盖后,需要在运行期间通过识别实际对象,才能确定调用哪一个方法。与继承相关的还有抽象类和最终类。抽象类不能用于实现对象的实例化。最终类不能被其它类所继承。

=================================

【Wed Jun 22 2016 16:31:56 GMT+0800】

 class Dog{
int age = 5;
String color = "Orange";
}
public class Test {
public static void main(String [] args){
Dog d = new Dog();
System.out.printf("%s\n", d); //Dog@71fdbe17
System.out.printf("%s\n", new Dog());//Dog@49cb1278
//Dog d = new Dog(); //Exception in thread "main" java.lang.Error: Unresolved compilation problem: Duplicate local variable d
d = new Dog();
System.out.println(d); //Dog@3329aa21
System.out.println(new Dog());Dog@746076c4
System.out.println(new Dog() == d);//false
System.out.println(d.age + "," +d.color);
System.out.println(new Dog().age + "," + new Dog().color);
System.out.println(new Dog().age == d.age);//true
}
}

java对象的本质是指针

理解java对象,第7行:

Dog d = new Dog();

Dog相当于变量类型。java 的变量类型也是只能声明一次。左侧相当于 int *a,右侧相当于 malloc(sizeof(int)) ,只是“相当于”。

java在声明变量时,必须明确:数据类型、变量名称及变量值。java不会为基本数据类型赋初值。因此,必须手动赋初值。

JavaSE自学笔记的更多相关文章

  1. JAVA自学笔记21

    JAVA自学笔记21 1.转换流 由于字节流操作中文不是非常方便,因此java提供了转换流 字符流=字节流+编码表 1)编码表 由字符及其对应的数值组成的一张表 图解: 2)String类的编码和解码 ...

  2. 《Linux内核设计与实现》课本第四章自学笔记——20135203齐岳

    <Linux内核设计与实现>课本第四章自学笔记 进程调度 By20135203齐岳 4.1 多任务 多任务操作系统就是能同时并发的交互执行多个进程的操作系统.多任务操作系统使多个进程处于堵 ...

  3. 《Linux内核设计与实现》课本第三章自学笔记——20135203齐岳

    <Linux内核设计与实现>课本第三章自学笔记 进程管理 By20135203齐岳 进程 进程:处于执行期的程序.包括代码段和打开的文件.挂起的信号.内核内部数据.处理器状态一个或多个具有 ...

  4. 《Linux内核设计与实现》课本第十八章自学笔记——20135203齐岳

    <Linux内核设计与实现>课本第十八章自学笔记 By20135203齐岳 通过打印来调试 printk()是内核提供的格式化打印函数,除了和C库提供的printf()函数功能相同外还有一 ...

  5. python自学笔记

    python自学笔记 python自学笔记 1.输出 2.输入 3.零碎 4.数据结构 4.1 list 类比于java中的数组 4.2 tuple 元祖 5.条件判断和循环 5.1 条件判断 5.2 ...

  6. ssh自学笔记

    Ssh自学笔记 Ssh简介 传统的网络服务程序,如:ftp.pop和telnet在本质上都是不安全的,因为它们在网络上用明文传送口令和数据,别有用心的人非常容易就可以截获这些口令和数据.而且,这些服务 ...

  7. JavaScript高级程序设计之自学笔记(一)————Array类型

    以下为自学笔记. 一.Array类型 创建数组的基本方式有两种: 1.1第一种是使用Array构造函数(可省略new操作符). 1.2第二种是使用数组字面量表示法. 二.数组的访问 2.1访问方法 在 ...

  8. vue 自学笔记记录

    vue 自学笔记(一): 知识内容:  安装vue ,创建vue实例,安装第一个组件,单项数据流 https://www.cnblogs.com/baili-luoyun/p/10763163.htm ...

  9. JS自学笔记05

    JS自学笔记05 1.例题 产生随机的16进制颜色 function getColor(){ var str="#"; var arr=["0","1 ...

随机推荐

  1. VS C#开发中WinForm中Setting.settings的作用

    .定义 在Settings.settings文件中定义配置字段.把作用范围定义为:User则运行时可更改,Applicatiion则运行时不可更改.可以使用数据网格视图,很方便: .读取配置值 tex ...

  2. 如何得到EF(ADO.NET Entity Framework)查询生成的SQL? ToTraceString Database.Log

    ADO.NET Entity Framework ToTraceString  //输出单条查询 DbContext.Database.Log  //这里有详细的日志

  3. 使用Goertzel算法识别DTMF信号

    Goertzel算法 Goertzel算法由Gerald Goertzel在1958年提出,用于数字信号处理,是属于离散傅里叶变换的范畴,目的是从给定的采样中求出某一特定频率信号的能量,用于有效性的评 ...

  4. 串口控RGB三色灯

    本文由博主原创,如有不对之处请指明,转载请说明出处. /********************************* 代码功能:串口控RGB三色灯 使用函数: Serial.flush(); / ...

  5. 足球游戏AI_资料收集

    实况足球中文官网 浅谈足球游戏的人工智能 用遗传算法加强足球游戏的人工智能 足球规则图解 守门员的技巧你知道吗? 教你足球守门员守门技术练习方法和技巧 足球守门员规则 判断点球方向

  6. android studio中断开SVN连接,并彻底清理项目中的.svn文件

    首先,断开SVN连接: 在使用SVN过程中,我们会发现当我们第一次share到subversion的时候,下次就无法重新share了,也无法断开连接,就算我们将工程目录下的.svn目录删除它还是会无法 ...

  7. configparser配置文件操作

    configparser 模块用于对配置操作  官方文档地址https://docs.python.org/3/library/configparser.html 导入configparser模块 i ...

  8. 集合类List,set,Map 的遍历方法,用法和区别

    遍历list: 方法一: for(String s:lists){ System.out.println(s); } 方法二: System.out.println("list with i ...

  9. C++拷贝构造函数

    拷贝构造函数是一种特殊的构造函数,其定义为第一个参数为为本类型的一个引用或者是常引用,且无其它参数或者其它参数为默认值,例如下面的函数: X::X(const X&); X::X(X& ...

  10. C语言实现 二分查找数组中的Key值(递归和非递归)

    基本问题:使用二分查找的方式,对数组内的值进行匹配,如果成功,返回其下标,否则返回 -1.请使用递归和非递归两种方法说明. 非递归代码如下: #include <stdio.h> int ...