多态
多态可以消除类型之间的耦合,也能够提高程序的可扩展性。
向上转型
一个类型继承基类,然后在初始化一个新的子类对象的时候,对这个子类的用法它既可以展现出基类的特性也可以展现出其本身类的特性。向上转型就是指在继承树中,基类是处于上层的,然后子类展现出父类或父类以上的类型特征,子类变成基类的类型。
示例代码:
//乐器基类
class Instrument{
public void play(Note n){
print("Instrument play()");
}
}
//继承的子类
public class Wind extens Instrument{
public void play(Note n){
System.out.println("Wind.play()" + n);
}
}
//程序运行的主类
public class Music{
public static void tune(Instrument i){
//...
i.play(NOTE_MIDDLE_C);
}
public static void main(String[] args){
Wind flute = new Wind();//新建一个子类wind对象flute
tune(flute);//调用的这个静态方法需要传入的参数类型是基类Instrument示例对象
//然而传入进去的是子类对象Wind实例,因此由于多态这个子类实例就向上转型为基类实例
}
}/*output:
Wind.play() MIDDLE_C//调用的play方法仍然是子类本身的play方法
*/
最简单的向上转型的例子
Shape s = new Cirle();
向上转型会缩小接口,但是不会比基类的接口更小
忘记对象类型?刚才的示例中,传入进去的Wind对象会向上转型为自己的基类Instrument,对于所有的对象都是这样设计的——会忘记其自身的对象类型。这样设计目的有:如果我们每个对象都只将其看作自己本身的类型实例,那么在每个基类的特殊类导出类的是时候,对一些基类的基本方法,每个导出类都需要各自编写一个相应的方法,这样会增加更多的代码量,并且在后期的代码中也会加大代码量,比如再写一个方法,就需要在导出类编写相应的方法。
转机
方法参数传递的时候,需要一个基类对象,在传递进一个导出类的时,他如何区别不是这个基类的其他导出类。比如传递进入的是乐器类的二胡,它如何判断是传递的二胡实例对象的引用,而不是小提琴的对象实例的引用。
方法调用绑定
对于java来说,方法的绑定除了static和final其余的都是后期绑定,另外两个是属于前期绑定。
前期绑定就是在程序执行前就进行绑定,后期绑定是指在程序运行时根据对象的类型进行动态的绑定。这两种很好理解,static方法是静态方法在对象初始化执行就被一次性加载,所以在执行前就绑定好了,表示这个类的成员静态方法,而对于其他的方法,在运行时才绑定,在进行具体的调用,这样就是后期绑定,也叫前期绑定。
通过动态绑定和前期绑定,可以对象方法调用的时候产生正确的行为。比如说,上面的示例代码中,调用的play方法仍然是子类本身的play方法。它是在运行时执行子类的具体方法,而不是基类的方法,比如在导出类会覆盖基类的方法时这种作用就很好理解了。
多态的两大缺陷
“覆盖”私有方法
对于基类和导出类来讲,如果我们的基类有些方法是私有的private,在其导出类中同样的名字的方法,此时这个导出类的方法不是通过继承而来的覆盖(override)方法,而是一个全新的基于该导出类的方法,在多态机制下,在进行导出类的该方法调用的时候,就不是执行这个导出类的方法,而是基类里面那个同名字的私有方法了。这就是多态的“覆盖”私有方法,让方法执行到基类的私有方法,而不是执行到同名的导出类的同名方法。
域与静态方法
首先,静态方法是与类而非单个对象相关联的。而多态是在程序运行过程中显现出来的特性,静态方法则是在编译时就完全确立好了的唯一一次加载。因此,静态方法可以说是与多态完全无关。
构造器和多态
在调用导出类的构造器之前,会按照基层层次逐渐向上链接,保证每个基类的构造器都能够被调用,这样做可以保证每个对象都能够被正确地创建,因为对于基类来讲,一般他的成员变量都是私有的,只有让基类的构造器先调用了,最后面的导出类才能具有正确的那些基类的成员变量。
构造器的调用顺序
- 在其他事物还没发生之前,先将分配给对象的存储空间初始化二进制的0(构造器中调用后期绑定的动态方法时会发生不一样)
- 调用基类的构造器,从继承树上面到最下面的导出类。
- 按照声明顺序调用成员的初始化方法
- 调用导出类的构造器主体
注意:在进行对象销毁的时候,是与构造器调用顺序相反的销毁对象的,原因很简单,在销毁对象的时候,可能会调用父类的方法,因此需要保证基类的对象存在。