第5章 继承
类、超类和子类
Object:所有类的超类
泛型数组列表
对象包装器和自动装箱
参数数量可变的方法
枚举类
反射
继承设计的技巧
5.1 类、超类和子类
关键字extends表明正在构造的新类派生于一个已存在的类。已存在的类称为超类(super class)、基类(base class)或父类(parent class);新类称为子类(subclass)、派生类(derived class)或孩子类(child class)。
在父类中的方法,子类不一定适用,需要提供一个新的方法来覆盖(override)超类中的这个方法。
调用父类的方法可以通过super解决这个问题,调用父类的构造,仅限子类构造中,super()。
注释:有些人认为super与this引用是类似的概念,实际上,这样比较并不太恰当。这是因为super不是一个对象的引用,不能将super赋值给另一个对象变量,他只是一个指示编译器调用超类方法的特殊关键字。
一个对象变量(例如,变量e)可以指示多种实际类型的现象被称为多态(polymorphism)。在运行时能够自动选择调用哪个方法的现象称为动态绑定(dynamic binding)。
5.1.1 继承层次
由一个公共超类派生出来的所有类的集合被称为继承层次(inheritance hierarchy);在继承层次中,从某个特定的类到其祖先的路径被称为该类的继承链(inheritance chain)。
5.1.2 多态
"is-a" 规则的另一种表述法是置换规则。他表明程序中出现超类对象的任何地方都可以用子类对象置换。
在Java程序设计语言中,对象变量是多态的。
5.1.3 动态绑定
1)编译器查看对象的声明类型和方法名。
2)接下来,编译器将查看调用方法时提供的参数类型。
3)如果是private方法、static方法,final方法或者构造器,那么编译器将可以准确的知道应该调用那个方法,我们将这种调用方式称为静态绑定(static binding)。
4)当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法。
虚拟机预先为每个类创建了一个方法表(method table),其中列出了所有方法的签名和实际调用的方法。
5.1.4 阻止继承:final类和方法
不允许扩展的类被称为final类。
类中的特定方法也可以被声明为final。如果这样做,子类就不能覆盖这个方法(final类中的所有方法自动地成为final方法)。
将方法或类声明为final主要目的是:确保它们不会在子类中改变语义。
5.1.5 强制类型转换
只能在继承层次内进行类型转换。
在将超类转换成子类之前,应该使用instanceof进行检查。
5.1.6 抽象类
从某种角度看,祖先类更加通用,人们只将它作为派生其他类的基类,而不作为想使用的特定的实例类。
抽象类不能被实例化。也就是说,如果将一个类声明为abstract,就不能创建这个类的对象。
5.1.7 受保护的访问
java用户控制可见性的4个访问修饰符:
1)仅对本类可见——private。
2)对所有类可见——public。
3)对本包和所有子类可见——protected。
4)对本包可见——默认(很遗憾),不需要修饰符。
5.2 Object:所有类的超类
Object类是Java中所有类的始祖,在java中每个类都是由它扩展而来的。
可以使用Object类型的变量引用任何类型的对象。
在java中,只有基本类型(primitive types)不是对象。所有的数组类型,不管是对象数组还是基本类型的数组都扩展与Object类。
5.2.1 equals方法
Object类中的equals方法用于检测一个对象是否等于另外一个对象。
5.2.2 相等测试与继承
java语言规范要求equals方法具有下面的特性:
1)自反性:对于任何非空引用x,x.equals(x)应该返回true。
2)对称性:对于任何引用x和y,当且仅当y.equals(x)返回true,x.equals(y)也应该返回true。
3)传递性:对于任何引用x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,x.equals(z)也应该返回true。
4)一致性:如果x和y引用的对象没有发生变化,反复调用x.equals(y)应该返回同样的结果。
5)对于任何非空引用x,x.equals(null)应该返回false。
不同的情况:
如果子类能够拥有自己的相等概念,则对称性需求将强制采用getClass进行检测。
如果由超类决定相等的概念,那么就可以使用instanceof进行检测,这样可以在不同子类的对象之间进行相等的比较。
下面给出编写一个完美的eauals方法的建议:
1)显示参数命名为(otherObject),稍后需要将它转换成另一个叫做other的变量。
2)检测this与otherObject是否引用同意个对象:
if(this==otherObject) return true;
3)检测otherObject是否为null,如果为null,返回false。这项检测是很必要的。
if(otherObject==null) return false;
4)比较this与otherObject是否属于同一个类。如果equals的语义在每个子类中有所改变,就使用getClass检测:
if(getClass()!=otherObject.getClass())return false;
如果所有的子类都拥有统一的语义,就使用instanceof检测:
if(!(otherObject intanceof ClassName)) return false;
5)将otherObject转换为相应的类类型变量:
ClassName other = (ClassName) otherObject;
6)现在开始对所有需要比较的域进行比较。使用==比较基本数据类型,使用equals比较对象域。如果所有的域都匹配,就返回true;否则返回false。
5.2.3 hashCode方法
如果重新定义equals方法,就必须重新定义hashCode方法,以便用户可以将对象插入到散列表中。
不过,在java7中还可以做两个改进。首先最好使用null安全的方法Objects.hashCode。如果其参数为null,这个方法会返回0,否则返回对参数调用hashCode的结果。
eauals与hashCode的定义必须一致:如果x.eauals(y)返回true,那么x.hashCode()必须与y.hashCode()具有相同的值。
5.2.4 toString方法
用于返回表示对象值的字符串。
5.3 泛型数组列表
ArrayList
ArrayList<Employee> staff = new ArrayList<Employee>();
5.4 对象包装器与自动装箱
所有的基本类型都有一个与之对应的类。这些对象包装器拥有很鲜明的名字:Integer、Long、Float、Double、Short、Byte、Character、Void和Boolean(前6个类派生于公共的超类Number)。对象包装器类是不可变的,类还是final。
自动装箱(基本类型自动转换成包装类),自动拆箱(包装类自动转换为基本类型)。
5.5 参数变量可变的方法
这里的省略号...是java代码的一部分,它表明这个方法可以接收任意数量的的对象。
允许将一个数组传递给可变参数方法的最后一个参数。
5.6枚举类
public enum Size{SMALL,MEDIUM,LARGE,EXTRA_LARGE}
实际上,这个声明定义的类型是一个类,他刚好有4个实例。
因此在比较两个枚举类型的值时,永远不需要调用equals方法,而直接使用"=="就可以了。
如果需要的话,可以在枚举类型中添加一些构造器、方法和域。当然、构造器只是在构造枚举常量的时候被调用。
5.7 反射
能够分析类能力的程序称为反射(reflective)。反射机制的功能极其强大,在下面可以看到,反射机制可以用来:
在运行中分析类的能力。
在运行中查看对象。
实现通用的数组操作代码。
利用Method对象,这个对象很像C++中的函数指针。
5.7.1 Class类
Object类中的getClass()方法将会返回一个Class类的实例。
类名.class获得一个Class类的实例。
Class.forName加载一个类。
虚拟机为每个类管理一个Class对象。因此,可以利用==运算符实现两个类对象比较的操作。
5.7.2 捕获异常
5.7.3 利用反射分析类的能力
在java.lang.reflect包中有三个类Field、Method和Constructor分别用于描述类的域、方法和构造器。
这三个类都有一个叫做getName的方法,用来返回项目的名称。Field类有一个getType方法,用来返回描述域所属类型的Class对象。Method和Constructor类有能够报告参数类型的方法,Method类还有一个报告返回类型的方法。这三个类还有一个叫做getModifiers的方法,它将返回一个整型数值,用不同的位开关描述public和static这样的修饰符使用状况。另外,还可以利用java.lang.reflect包中的Modifier类的静态方法分析getModifiers返回的整型数值。例如,可以使用Modifier类中的isPubli、isPrivate或isFinal判断方法或构造器是否是public、private或final。另外,还可以利用Modifier.toString方法将修饰符打印出来。
Class类中的getFields、getMethods和getConstructors方法将分别返回类提供的public域、方法和构造器数组,其中包括超类的公有成员。Class类的getDeclareFields、getDeclareMethods和getDeclaredConstructors方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员。
5.7.4 在运行是使用反射分析对象
5.7.5 使用反射编写泛型数组代码
5.7.6 调用任意方法
利用Method类中的invoke方法。
5.8 继承设计的技巧
1)将公共操作和域放在超类。
2)不要使用受保护的域。
3)使用继承实现"is-a"。
4)除非所有继承的方法都有意义,否则不要使用继承。
5)在覆盖方法时,不要改变预期的行为。
6)使用多态,而非类型信息。
无论什么时候,对于下面这种形式的代码
if(x is of type1)
action1(x);
else if(x is of type2)
action2(x);
都应该考虑使用多态性。
7)不要过多地使用反射。