第八章 多态

在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本形态

  • 多态通过分离做什么和怎么做,从另一个角度将接口和实现分离开来。
  • “ 封装 ” 通过合并特征和行为来创建新的数据类型。
  • 多态的作用是消除类型之间的耦合关系。
  • 多态意味着 “ 不同的形式 ” ,在面向对象的程序设计中,我们持有从基类继承而来的相同接口,以及使用这个接口的不同形式:不同版本的动态绑定方法。

1.向上转型

  • 对象既可以作为他自己本身的类型使用,也可以作为它的基类型使用。这种把对某个对象的引用视为对其基类型的引用的做法称为向上转型-----这是因为,在继承图的画法中,基类是放置在上方的。
  • 一个导出类向上转型到基类,可能会 “缩小” 接口,但不会比 基类 的全部接口更窄。
  • 不管导出类,编写的代码只是与基类打交道,会更好,这也正是多态所允许的。

2.绑定

  • 将一个方法调用连同一个方法主体关联起来被称为绑定。
  • 如果在程序执行之前进行绑定 ( 如果有的话,由编译器和连接程序实现 ) ,叫做前期绑定。
  • 在运行时根据对象的类型进行绑定叫做后期绑定。后期绑定也被称为动态绑定或者运行时绑定。
  • Java 中除了static 方法和 final 方法 ( private 属于 final 方法 ) 之外,其他所有的方法都是后期绑定。后期绑定会自动发生。
  • final 除了可以防止别人覆盖这个方法之外,还可以有效地关闭动态绑定 。
  • 一旦知道了 Java 中的所有方法都是通过动态绑定实现多态这个事实之后,我们就可以编写只与基类打交道的程序代码了,并且这些代码对所有的导出类都可以正确运行。或者换一种是说法,发消息给某个对象,让该对象去断定应该做什么事。
  • 我们期望多态所具有的特性:我们所做的代码修改,不会对程序中其他不应该受到影响的部分产生破坏。换句话说,多态是一项让程序员 “ 将改变的事物与未改变的事物分离开来 ” 的重要技术 。

3. 缺陷: “ 覆盖 ” 私有方法

只有非 “ private” 方法才可以被覆盖,但是还需要密切注意 private 方法的现象。确切来说,在导出类中,对于基类中的 private 方法,最好采用不同的名字。


4. 缺陷 “ 域与静态方法 ”

  • 只有普通的方法调用可以是多态的。
  • 如果某个方法是静态的,它的行为就不具有多态性
  • 静态方法是与类,而并非与单个的对象相关联的。

5.构造器和多态

  • 构造器不同于其他种类的方法。尽管构造器不具有多态(他们实际上是 static 静态方法,只不过这个 static 是隐式的 )
  • 基类的构造器总是在导出类的构造过程中过调用。而且按照继承层次逐渐向上链接,以使每个基类的构造器都得以调用,这样做是有意义的,因为构造器具有一项特殊任务,检查对象是否被正确的构造。导出类只能够访问他自己的成员,不能访问基类中的成员(基类成员通常都是 private 类型) 。所以只有基类的构造器才具有恰当的知识和权限来对自己的元素进行初始化。因此必须令所有的构造器都得到调用。否则就不能正确的构造完成对象。这正是为什么编译器要强制每个导出类部分都必须调用构造器的原因。在导出类的构造器主体中,如果没有明确的指出调用某个基类的构造器,他就会默默的调用默认的构造器。如果不存在默认的构造器,编译器就会报错(如果某个类没有构造器,编译器会自动的合成一个默认的构造器):

    1. 调用基类构造器。这个步骤会不断反复地重复地递归下去,首先是构造这种层次结构的根,然后是下一层导出类,知道最底层的导出类。
    2. 按照声明顺序调用成员的初始化方法
    3. 调用导出类构造器的主体。
  • 在构造器内部,我们必须确保所要使用的成员都已经构建完毕,为了确保这一目的,唯一的办法就是首先调用基类的构造器。

6 . 继承与清理

  • 子对象通常都会留给垃圾回收器进行清理,但是由于继承的缘故,如果我们有其他作为垃圾回收一部分的他叔清理动作,就必须在导出类中覆盖 dispose() 方法 。当覆盖被继承类的 dispose 方法的时候 ,一定要调用基类版本的 dispose 方法,否则,基类的清理动作就不会发生。
  • 如果某个字对象要依赖于其他对象,销毁的顺序应该和初始化顺序相反。先对导出类进行清理,之后再对基类进行清理

7. 构造器内部的多态方法的行为

  • 在一般的方法内部,动态绑定的调用是在运行的时候才决定的,因为对象无法知道他是属于方法所在的那个类 , 还是属于那个类的导出类。
  • 如果要调用构造起的内部一个动态绑定方法,就要用到那个方法的被覆盖后的定义
  • 从概念上讲,构造器的工作实际上是创建对象。

8.初始化的实际过程

  • 在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零
  • 调用基类的构造器
  • 按照声明的顺序调用成员的初始化方法
  • 调用导出类的构造器主体

尽可能简单的方法使对象进入正常状态,如果可以的话,避免调用其他方法。

可以使用协变返回类型。


9 . 用继承进行设计

  • 在设计时,首先悬着的应该是 “ 组合 ” ,尤其是不能十分确定应该使用哪一种方式的时候。
  • 一条通用的准则:用继承表达行为间的差异,并用字段表达状态上的变化。

10 . 纯继承与扩展

  • 只有在基类中已经建立的方法才可以在导出类中被覆盖,这种被称为是纯粹的( is -a )关系,因为一个接口的类已经确定了他应该是什么。也可以认为这是一种纯代替,因为导出类可以完全代替基类。
  • is - like -a (像一个)关系,因为导出类就像是一个基类,他们有着相同的基本接口,但是它还具有额外的方法实现其他特性。

11 . 向下转型与运行时类型识别

  • 由于向上转型(在继承层次中向上移动)会丢失具体的类型信息,所以我们想通过向下转型-----也就是在继承层次中向下移动,应该能够获取类型信息。
  • 在java中,所有的转型都会得到一次检查,这种在运行期间对类型进行检查的行为称作是 “ 运行时类型识别 ( RTTI ) ” , RTTI 不仅包括转型处理,还有一种方法可以让你在转型之前查看你所要处理的类型。

由本人从 Thinking in java ( java 编程思想 ) 整理而来

最后修改:2018 年 07 月 15 日
哇卡哇卡