Java语言的三大特征正是封装、继承以及多态,在前边总结了一切皆对象和复用类两篇,正是封装以及继承。三者的作用如下:
- 封装:通过合并特征和行为而创建新的数据类型
- 继承:复用已有接口,将接口与实现分离
- 多态:消除类型之间的耦合
向上转型
在复用类的一章中其实就有接触到向上转型,表示为对某个对象的引用视为对其父类的引用。而向上转型可能会缩小接口范围,但是至少也是父类接口的大小。正因为此,便可节省大量的重复性代码。例如书中的例子:
1 | public class Chapter8 { |
在调用tune方法时,如果不使用向上转型,那么传入的参数是什么类型就要写成什么类型,这里的乐器有三种就要重载三个tune方法(如下),而且每新增一种乐器就要重载一个新的tune方法,这样修改起来就会很麻烦,也容易出错。而这向上转型正是多态允许的,当然也是继承的一个作用。
1 | private static void tune(Wind wind) { |
说到这里可能会好奇,这是怎么实现向上转型后,依旧能调用到正确的方法的呢?这正是接下来要说的方法调用绑定。
方法调用绑定
方法的绑定分为前期绑定和后期绑定。
- 绑定:一个方法的调用和一个方法的主体关联的过程就叫绑定
- 前期绑定:程序执行钱惊醒绑定
- 后期绑定:在程序运行时更具对象类型进行绑定
正是这个后期绑定为向上转型后依旧能正确调用方法主体提供了解决办法,在Java中除了static和final的方法外都是后期绑定。因为这些在继承中是不能够被重写的,所以也就有了将方法申明为final能提高一定效率的说法。但是建议使用final关键字是根据设计理念出发,而不是提高效率而言,因为这对程序本身提高的效率不是很大。
有了多态机制(向上转型-后期绑定),在程序设计的过程中就能更好的扩展,在父类中新增新的方法,也能在调用父类的接口中,不用修改就能照旧使用,并使用根据父类派生出来的子类。做到了将改变的事物与不变的事物相分离。
上文中说到除了static和final的方法外都是后期绑定,所以private的方法和static的域和方法都是无能被覆盖的,无法被重写,如果方法或字段名相同,那么就会容易让人混淆,所以最好采用不同的名字。
构造器与多态
初始化顺序
在之前的文章中,我总结出了一个类的初始化过程中,各个位置的变量、静态变量以及构造器的调用顺序,也是需要在这章中理解的,顺序如下:
父类静态变量>子类静态变量>父类变量>父类构造函数>子类变量>子类构造函数
同一个类中同一级别的调用顺序和代码顺序有关
为什么先初始化父类
子类被创建为什么要先调用父类的构造器呢?是因为子类只能访问父类的public和protected的字段和方法,而private的字段和方法无法直接访问,那么如果使用子类去初始化这个类,那么就会发现private的部分就无法被初始化,所以private的部分只能父类去初始化,这样才能保证在子类中要使用到父类的内容时,都是被初始化了的。
注:这里的初始化并非只数据的初始化,更像是某个事物被创建,即使被创建后是null。就像某个事物不存在到存在的过程
在父类的构造器中调用被重写的方法,若该方法中调用了子类的字段,则会导致,该字段还未被初始化,只有一个默认值。所以应该尽可能避免在构造器中调用能被重写的方法。
还有一点,重写的方法有返回值,那么返回值的类型可以是父类方法返回类型的子类。
总结
多态,在设计过程中,核心就是使用继承的方式,而对于设计程序,应该更多考虑组合的形式,除非你确定这两个类的关系是”is-a”的关系而不是”is-like-a”。
可以看到多态是在封装和继承的基础上存在的特性,这些特性便是程序设计的基石,只有多见识如何设计,多实践才能更好的用好这些特性,写出更好维护和扩展的代码。