4.多态(Thinking in java学习四)
多态
通过分离做什么和怎么做,从另一角度将接口和实现分离开来。多态不但能够改善代码的组织结构和可读性,还能够创建可扩展的程序。
==多态存在的三个必要条件==:
- 要有继承
- 要有重写
- 父类引用指向子类对象
转机
方法调用绑定
- 绑定:将一个方法调用同(与)一个方法主体关联起来被称作绑定。
- 前期绑定:若在程序执行前绑定,叫做前期绑定。
- 后期绑定(动态绑定,运行时绑定):在运行时根据对象的类型进行绑定。
编译器一直不知道对象的类型,但是方法调用机制能找到正确的方法体,并加以调用。Java中除了static方法和final方法之外,其他所有方法都是后期绑定。
注意:final的使用可以有效地关闭动态绑定。
产生正确的行为:
Java中的所有方法都是通过动态绑定实现多态的。
可扩展性
在一个良好的OOP程序中,大多数或者所有方法都会遵循tune()的模型,而且只与基类通信。
“覆盖”私有方法
只有非private方法才可以被覆盖。导出类中,对于基类的private方法,最好采用不同的名字。即使子类存在父类private方法相同的方法也不会覆盖,而是当作两个独立的方法。
域与静态方法
域与静态方法不存在多态,即不需要动态绑定。
注意:静态方法属于类,并不是与单个对象相关联的。
构造器和多态
构造器是static方法,构造器如何在通过多态在复杂的层次结构中运作?
构造器的调用顺序
基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上连接,以使每个基类的构造器都能得到调用。
注意:构造器有一项特殊任务:检查对象是否被正确地构造。
所以调用每个基类的构造器,才有权限和正确初始化每个基类的构造,这样才能完整且正确地构造出一个对象。
继承与清理
通过组合与继承方法来创建新类时,永远不用担心对象的清理问题,子对象通常都会留给垃圾回收器处理。若真要进行清理,则需要特意为该类写dispose()的清理方法。在覆盖清理方法时,需要先调用基类清理方法。
对象销毁顺序应该与初始化顺序相反。
构造器内部的多态方法的行为
问题:如果一个构造器的内部调用正在构造的对象的某个动态绑定方法,会发生什么?
分析:一般的方法内部,动态绑定的调用是在运行时才决定的。在基类构造器中调用导出类中覆盖基类的方法,此时会发生调用混乱。
注意:用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其他方法。构造器中唯一能安全调用的方法是基类中的final和private方法。
初始化的实际过程(完整):
- 在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零;
- 如前所述那样调用基类构造器,此时,调用被覆盖后的draw()方法;
- 按照声明的顺序调用成员的初始化方法;
- 调用导出类的构造器主题;
协变返回类型
Java SE5中新增了协变返回类型,它表示在导出类中的被覆盖方法可以返回基类方法的返回类型的某种导出类型。
协变返回类型允许覆盖方法时,将基类方法返回类型修改为其返回类型的某种导出类型。
用继承进行设计
组合与继承设计的区别:
组合能在运行期间决定切换不同对象,获得了动态灵活性,但是我们不能在运行期间决定继承不同的对象。
设计原则:用继承表达行为间的差异,并用字段表达状态上的变化。
class Actor { public void act(){} }
class HappyActor(){ public void act(){ print("HappyActor");} }
class SadActor(){ public void act(){ print("SadActor");}}
class stage{
private Actor actor = new HappyActor();
public void change(){ actor = new SadActor();}
public void performPlay(){ actor.act(); }
}
纯继承与扩展
采取纯粹的方式来创建继承层次结构似乎是最好的方式,即只有在基类中已经建立的方法才可以在导出类中被覆盖。
is-a 纯粹的继承,is-like-a 有着相同的基本接口,但是它还具有其他的额外方法。
向下转型与运行时类型识别
基类转导出类称为向下转型。
Java进入运行期时仍然会对类型进行检查,这种在运行期间对类型进行检查的行为称为“运行时类型识别”(RTTI)。