5. 继承
利用继承,人们可以基于已存在的类构造一个新类。继承已存在的类就是复用(继承)这些类的方法和域。
反射是指在程序运行期间发现更多的类及其属性的能力。
5.1 类、超类和子类
5.1.1 定义子类
public class Student extends Person{
//方法和域
}
关键字extends
表名正在构造的新类派生于一个已存在的类。已存在的类成为超类、基类或父类;新类称为子类、派生类或孩子类。
5.1.2 覆盖方法
在子类中可以增加域、增加方法或覆盖超类的方法,但是绝对不能删除集成的任何域和方法。
在Java中使用关键字super
调用超类的方法。
5.1.3 多态
多态:同一类的对象收到相同消息时,会得到不同的结果。而这个消息是不可预测的。多态,顾名思义,就是多种状态,也就是多种结果。
多态性是一种通过多种状态或阶段描述相同对象的编程方式。它的真正意义在于:实际开发中,只要关心一个接口或基类的编程,而不必关心一个对象所属于的具体类。
在Java程序设计语言中,对象变量是多态的。一个Student变量既可以引用一个Student类对象,也可以引用一个Student类的任何一个子类的对象。
在Java中,子类数组的引用可以转换成超类数组的引用,而不需要采用强制类型转换。
5.1.4 阻止继承:final类和方法
不允许扩展的类被称为final类。如果在定义类的时候使用了final修饰符就表名这个类是final类。
5.1.5 抽象类
如果自下而上在类的继承层次结构中上移,位于上层的类更具有通用性,甚至可能更加抽象。
使用abstract
关键字实现抽象类。
建议尽量将通用的域和方法放在超类中。
扩展抽象类的两种选择:在抽象类中定义部分抽象类方法或不定义抽象类方法,这样就必须将子类也标记为抽象类;定义全部的抽象方法。
- 类即使不含抽象方法,也可以将类声明为抽象类。
- 抽象类不能被实例化。也就是说,如果将一个类声明为abstract,就不能创建这个类的对象。
5.1.6 受保护访问
- 仅对本类可见--private
- 对所有类可见--public
- 对本包和所有子类可见--protected
- 对本包可见--默认,不需要修饰符
5.2 Object:所有类的超类
Object类是Java中所有类的始祖,在Java中每个类都是由它扩展而来的。
如果没有明确地指出超类,Object就被认为是这个类的超类。
可以使用Object类型的变量引用任何类型的对象:
Object obj = new Employee("Hello World", 35000);
Object类型的变量只能用于作为各种值得通用持有者。要想对其中的内容进行具体的操作,还需要清楚对象的原始类型,并进行响应的类型转换:
Employee e = (Employee)obj;
所有的数组类型,不管是对象数组还是基本类型的数组都扩展了Object类。
5.2.1 equals方法
Object类中的equals方法用于检测一个对象是否等于另外一个对象。在Object类中,这个方法将判断两个对象是否具有相同的引用。
在子类中定义equals方法时,首先调用超类的equals。如果检测失败,对象就不可能相等。如果超类中的域都相等,就需要比较子类中的实例域。
5.2.2 相等测试与继承
Java语言规范要求equals
方法具有以下特性:
- 自反性:对于任何非空引用x,
x.equals(x)
应该返回true - 对称型:对于任何引用x和y,当且仅当
y.equals(x)
返回true,x.equals(y)
也应该返回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
如果子类能够拥有自己的相等概念,则对称型需求将强制采用getClass
进行检测
如果由超类决定相等的概念,那么就可以使用instanceof
进行检测,这样可以在不同子类的对象之间进行相等的比较。
Java核心技术给出的equals
方法建议:
显式参数命名为
otherObject
,稍后需要将它转换成另一个叫做other的变量检测this与
otherObject
是否引用同一个对象:if(this == otherObject) return true;
检测
otherObject
是否为null,如果为null,返回false。这项检测是很必要的。if(otherObject == null) return false;
比较
this
与otherObject
是否属于同一个类。如果equals的语义在每个子类中有所改变,就使用getClass检测:if(getClass != otherObject.getClass()) return false;
如果所有的子类都拥有统一的语义,就是用
instanceof
检测:if(!(otherObject instanceof ClassName)) return false;
将
otherObject
转换为相应的类类型变量:ClassName other = (ClassName)otherObject
现在开始对所有需要比较的域进行比较了。使用
==
比较基本类型域,使用equals比较对象域。如果所有的域都匹配,就返回true;否则返回false。
如果在子类中重新定义equals,就要在其中包含调用super.equals(other)
。
5.2.3 hashCode方法
散列码(hash code)是由对象导出的一个整形值。散列码是没有规律的。如果x和y是两个不同的对象,x.hashCode()
与y.hashCode()
基本上不会相同。
由于hashCode
方法定义在Object类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。
最好使用null安全的访法Objects.hashCode
。如果其参数为null,这个方法会返回0,否则返回对参数调用hashCode的结果。另外,使用静态方法:Double.hashCode可以避免创建Double对象。
5.2.3 toString方法
用于返回表示对象值的字符串。toString方法也可以供子类调用。
5.3 对象包装器与自动装箱
包装器(wrapper):Integer、Long、Float、Double、Short、Byte、Character、Void和Boolean(前6个派生于公共的超类Number)。对象包装器类是不可变的,一旦构造了包装器,就不允许更改包装在其中的值。同时包装器类还是final,因此不能定义它们的子类。
ArrayList<Integer> list = new ArrayList<>();
list.add(3); // => list.add(Integer.valueOf(3)) 自动装箱
int n = list.get(i); // = > int n = list.get(i).intValue(); 自动拆箱
5.4 枚举类
先看一个例子:
public enum Size {
SMAL,MEDIUM,LARGE,EXTRA_LARGE;
}
这个声明定义的类型是一个类,有四个实例。
所有的枚举类型都是Enum
类的子类。它们继承了Enum
类的许多方法。包含toString
,它能够返回枚举常量名。例如Size.SMALL.toString()
将返回字符串"SMALL"
。toString
的逆方法是静态方法valueOf
,例如:Size s = Enum.valueOf(Size.class,"SMALL");
每个枚举类型都有一个静态的values
方法,它将返回一个包含全部枚举值的数组。
5.5 反射
能够分析类能力的程序称为反射。反射功能及其强大:
- 在运行时分析类的能力
- 在运行时查看对象
- 实现通用的数组操作代码
- 利用Method对象。
5.5.1 Class类
Object类中的getClass()
方法将会返回一个Class类型的实例。Class用于保存类的信息。
可以调用静态方法forName
获得类名对应的Class对象。
5.5.2
5.6 继承的设计技巧
- 将公共操作和域放在超类
- 不要使用受保护的域
- 使用继承实现“is-a”关系
- 除非所有继承的访法都有意义,否则不要使用继承
- 在覆盖方法时,不要改变预期的行为
- 使用多态,而非类型信息
- 不要过多的使用反射