【Java核心计算 基础知识(第9版)】第5章 继承
本章要点
- 类、超类和子类
- Object:所有类的超类
- 泛型数组列表
- 对象包装器和自动装箱
- 参数数量可变的方法
- 枚举类
- 反射
- 继承设计的技巧
5.1 类、超类和子类
- 关键字extends表明正在构造的新类派生于一个已存在的类。已存在的类称为超类(superclass)、基类(base class)、父类(parent class);新类称为子类(subclass)、派生类(derived class)、孩子类(child class)。
- 设计类时,通用方法声明在超类中,子类特有的方法声明在子类中。
- 对于超类中不适用于子类的方法,子类可以将其覆盖(override)。
- 如上一篇博文所指出的:Java Language Specification(JavaSE 8)-8.5. Member Type Declarations明确指出,超类的私有成员不被继承。
- super关键字可指示调用超类(超类,而不是父类对象)成员和构造器。
- 子类构造器必须首先调用超类构造器。如果子类构造器没有显式调用超类构造器,则将自动调用超类默认(无参)的构造器。注意此时如果超类没有无参构造器,编译器将报错。
5.1.1 继承层次
- 由一个公共超类派生出来的所有类的集合被称为继承层次(inheritance hierarchy)。在继承层次中,从某个特定的类到其祖先类的路径被称为该类的继承链(inheritance chain)。
5.1.2 多态
- 置换法则:程序中出现超类对象的任何地方都可以用子类对象置换。即,子类的每个对象也是超类的对象。
- 多态:父类变量既可以引用父类对象,也可以引用任一子类对象(父类引用指向子类对象)。
- 注意:由于多态的存在,数组是类型不安全的,需要时刻牢记创建时的元素类型。
public static void main(String[] args) {
Integer[] irr = new Integer[10];
Object[] orr = irr;
orr[0] = new Object();// java.lang.ArrayStoreException: java.lang.Object
}
5.1.3 动态绑定
对象方法的调用过程:
1. 编译器查看对象的声明类型和方法名。编译器会获取对象内所有可访问的同名方法。
2. 编译器查看调用方法时提供的参数列表。如果存在一个与提供的参数类型完全匹配,就选择这个方法,这个过程被称为重载解析(overriding resolution)。如果参数类型存在父子关系,子类型比父类型更匹配。如果都不匹配,或匹配不止一个,则报错。
3. 如果是private,static,final方法或者构造器,那么编译器可以准确的知道应该调用哪个方法,这种方式称为静态绑定(static binding)。其他方法的调用依赖于隐式参数(this)的运行时类型,称为动态绑定(dynamic binding)。
4. 动态绑定的调用,虚拟机在程序运行时确定与this的实际类型最匹配的方法。虚拟机会预先为每个类创建一个方法表,列明所有的方法签名和实际调用的方法。如果方法通过super调用,编译器会通过超类进行确定。
动态绑定的一个重要特性:当为方法的调用增加新的可能性时(例如,提供一个新的实现),无需修改、也无需重新编译调用的代码。
5.1.4 阻止继承:final类和方法
- 被final修饰的类不可以被继承。
- 被final修饰的方法不可以被覆盖。
- 被final修饰的变量只能赋值一次。
5.1.5 强制类型转换
- 将一个类型强制转换为另一个类型的过程被称为强制类型转换。
- (Type) object
- 进行类型转换的唯一原因是:在暂时忽视对象的实际类型之后(向上转型),使用对象的全部功能(强制向下转型)。
- 转换失败:Cannot cast from ** to **(继承层次外,编译期),ClassCastException(继承层次内,运行期)。
- 只能在继承层次内进行类型转换。
- 在将超类转换为子类之前,应该使用instanceof进行检查。
5.1.6 抽象类
- 用abstract修饰的方法为抽象方法,用abstract修饰的类为抽象类。
- 抽象类不一定有抽象方法,有抽象方法的类一定是抽象类或接口。
- 抽象类不能实例化。(可以声明抽象类型的变量引用其实现类对象。)
- 抽象类的子类必须实现抽象类内所有的抽象方法,或者声明为抽象类。
- abstract不可以和static,final,private修饰同一个方法,因无意义。
5.2 Object:所有类的超类
- Object类是Java中所有类的最终超类,如果没有明确声明超类,则Object类是该类的直接超类。
- 基本类型不是Object的子类,基本类型字面值不是对象。
5.2.1 equals方法
- equals()用于检测一个对象是否等于另一个对象。
- 在Object类中,引用相同(即引用了同一个对象)返回true,否则返回false。
- 在定义类时,通常重写equals(),状态相同(域的值相等)的两个对象即相等。
5.2.2 相等测试与继承
Java语言规范要求equals()具有一下特性:
- 自反性:对于任何非空引用x,x.equals(x)应该返回true。
- 对称性:对于任何引用x和y,当且仅当x.equals(y)返回true时,y.equals(x)返回true。
- 传递性:对于任何引用x、y和z,当x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)返回true。
- 一致性:如果x和y引用的对象没有发生变化,反复调用x.equals(y)应该返回同样的结果。
- 对于任意非空引用x,x.equals(null)应该返回false。
建议:在比较x与y是否属于同一个类时,如果equals的语义在每个子类中都一样(父类中将equals()声明为final),则用instanceof判断;如果equals的语义在每个子类中可能不同,则用getClass()判断。
- 这是因为,sub instanceof SuperType返回true,super instanceof SubType返回false,违反了对称性要求。
5.2.3 hasCode方法
- 散列码(hash code)是由对象导出的一个无规律的整型值。不同对象的散列码一般不相等。
- Object类中,hashCode()返回对象的地址值。
- 如果重写equals(),一般同时重写hasCode(),以便将对象的状态反映到散列码中。在重写的equals()中,先比较散列码。
- equals()与hashCode()必须一致:equals()返回true的两个对象要求返回相等的散列码。但具有相等的散列码的两个对象equals()不一定返回true。
5.2.4 toString方法
- toString()返回表示对象值的字符串。
- Object类中,toString()返回对象的字符串格式为”类名@十六进制散列码”
- 一般重写toString()方法,返回”类名[field=value,field=value…]”格式的字符串。
5.3 泛型数组列表
- ArrayList类
5.4 对象包装器与自动装箱
- 每个基本类型都有一个与之对应的类,称为它的包装器(wrapper)。Byte,Short,Integer,Long,Float,Double,Character,Boolean,Void.
- Number类是Byte,Short,Integer,Long,Float,Double的公共超类。
- 对象包装器类是不可变的,即一旦构造类包装器对象,就不允许更改包装纸其中的值。
- 当有需要时,基本类型可以自动装箱(autoboxing)为包装器,包装器也可以自动拆箱为基本类型。
- 介于-128~127之间的值会被包装到固定的对象中。
Integer i1 = 127;
Integer i2 = 127;
System.out.println(i1 == i2); // true
Integer l1 = new Integer(127);
Integer l2 = new Integer(127);
System.out.println(l1 == l2); // false
- 自动装箱和自动拆箱由编译器实现,而不是由虚拟机实现。
5.5 参数数量可变的方法
- 指可以用可变的参数数量调用的方法。
- (Type1 name, Type1 name,Type3… names)中,…表示这个方法可以接收任意数量Type3类型的参数。
- Type3…实际上是一个Type3[]数组,因此也可以直接接收一个数组对象(前提是该可变参数位于参数列表的末尾)。
5.6 枚举类
- 声明了有限个实例的类。
public static void main(String[] args) {
System.out.println(Size.SMALL); // SMALL
System.out.println(Size.MEDIUM); // MEDIUM
System.out.println(Size.LARGE); // LAGRE
System.out.println(Size.SMALL.getValue()); // 1
System.out.println(Size.MEDIUM.getValue()); // 2
System.out.println(Size.LARGE.getValue()); // 3
System.out.println(Enum.valueOf(Size.class, "SMALL")); // SMALL
System.out.println(Enum.valueOf(Size.class, "MEDIUM")); // MEDIUM
System.out.println(Enum.valueOf(Size.class, "LARGE")); // LAGRE
System.out.println(Size.SMALL.ordinal()); // 0
System.out.println(Size.MEDIUM.ordinal()); // 1
System.out.println(Size.LARGE.ordinal()); // 2
}
enum Size{
SMALL(1),MEDIUM(2),LARGE(3);
private int value;
private Size(int value){
this.value = value;
}
public int getValue(){
return value;
}
}
5.7 反射
5.7.1 Class类
- 在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。
- 可以通过Class类访问这些信息。有三种方法可以获取Class类型实例。
- Object类的方法getClass()(通过对象获取);
- Object类的静态方法forName()(通过类名获取);
- 任意类型T,T.class(通过类型获取);
- 一个Class对象表示的是一个类型,而不是一个类。如int.class,void.class。
- Class类的newInstance()方法调用默认的无参构造器构造一个对象。
5.7.3 利用反射分析类的能力
- java.lang.reflect包下的三个类:Field,Method,Constructor。
- Class类的getFields(),getMethods(),getConstructor()分别返回对象运行时类型的public域,方法,构造器,包括父类的public域和方法,不包括父类的public构造器。
- Class类的getDeclaredFields(),getDeclaredMethods(),getDeclaredConstructors()分别返回全部域,方法,构造器,但不包括父类的域,方法和构造器。
- Modifier类(getModifiers())和Parameter类(getParameterTypes())。
- getExceptionTypes(),getReturnType()。
5.7.4 在运行时使用反射分析对象
- AccessibleObjet类的setAccessible()方法。
- Field类的get()和set()方法。
- get()和set()方法返回和接收基本类型是通过包装器实现的,如果要直接返回/接收基本类型,应调用getInt()等方法。
- Method类的invoke()方法。
- Constructor类的newInstance()方法。
5.7.5 使用反射编写泛型数组代码
- Class类的getComponentType()方法:返回数组元素类型,当对象不是数组时返回null。
- java.lang.reflect.Array类的newInstance()方法。
- 数组是对象,基本类型不是对象;所基本类型数组可以转换为Object,但不能转换为Object[]。
5.8 继承设计的技巧
- 将公共操作和域放在超类
- 不要使用protected域。
- 使用继承实现”is-a”关系。
- 除非所有继承的方法都有意义,否则不要使用继承。
- 在重写方法时,不要改变预期的行为。
- 使用多态,而非类型信息。
- 不要过多地使用反射。