本博客用来记录阅读《thinking in Java》和多态有关的信息
多态例子
先来看个代码:
class Instrument { public void play(Note n) { print("Instrument.play()"); } } //: polymorphism/music/Wind.java package polymorphism.music; public class Wind extends Instrument { public void play(Note n) { System.out.println("Wind.play() " + n); } } //: polymorphism/music/Music.java package polymorphism.music; public class Music { public static void tune(Instrument i) { // ... i.play(Note.MIDDLE_C); } public static void main(String[] args) { Wind flute = new Wind(); tune(flute); // Upcasting } } /* Output: Wind.play() MIDDLE_C
我们看到,这个Music类的tune明明是接收一个Instrument类作为参数,我们传给它了个Wind类的对象,然后调用方法,从输出来看竟然正确地输出了Wind类的play方法?
其实把这个Wind的对象交给一个Instrument引用这个可以理解,就是类似于:
Instrument i = new Wind();
因为这个Wind是Instrument,换句话就是一个乐器,是一个Instrument。所以这样赋值是有道理的,但在tune方法里面,i调用了父类的play方法,而最终输出的调用的是Wind对象的play方法,这是怎么做到的呢?
这就是一个多态的表现。
方法调用绑定
为什么我们会觉得上面的代码奇怪呢?
将方法的调用和方法体连接在一起就叫做方法绑定,如果是在程序运行之前绑定,或者说是编译器或者是链接器绑定的,我们就叫它前期绑定(early binding)。你可能没有听说过这个名词,因为面向过程语言中,不用选择就是默认为前期绑定,像C语言只有一种方法调用,就是前期绑定。
上面的程序之所以会迷惑你,就因为你带着前期绑定的思想去看它,因为编译器在编译的时候,只有一个Instrument引用i,它没办法知道究竟调用哪个方法才对。
有前期绑定,就肯定有后期绑定(late binding)。没错,后期绑定就解决上面不知道调用哪个方法的问题。它的涵义就是在运行的过程中根据对象的类型进行绑定。后期绑定也叫动态绑定(dynamic binding)或者是运行时绑定(runtime binding)。
如果一个语言要实现动态绑定,那么一定要有个机制可以在运行的时候判断对象的类型,从而调用正确的方法。 也就是说,编译阶段,编译器一直都不知道对象的类型。后期绑定机制随着语言的不同会有所不同,但想下就知道,应该在对象中安排某种“类型信息”。
Java除了static方法,final方法之外,其他方法的调用都是后期绑定的机制——也就是意味着一般我们不用判断是否需要后期绑定,它会自动发生。(注意,private的方法默认为final方法,也不用后期绑定机制)
当你把一个方法声明为final的时候,一来是防止别的人覆盖重写该方法,二来是可以自动“关闭”动态绑定。
注意的地方
1.private方法是自动被当成final方法,所以用的不是动态绑定机制,而且子类无法重写:
这里看个例子:
//: polymorphism/PrivateOverride.java package polymorphism; import static net.mindview.util.Print.*; public class PrivateOverride { private void f() { print("private f()"); } public static void main(String[] args) { PrivateOverride po = new Derived(); po.f(); } } class Derived extends PrivateOverride { public void f() { print("public f()"); } } /* Output: private f() *///:~
父类中有个private的方法f(),然后子类继承父类后,试图重写override这个f()。
我们看到在main函数中,父类引用PrivateOvrride指向子类对象new Derived()。
然后试图用父类引用po调用f()方法。
如果按照我们后期绑定的思想,这里应该是输出public f(),但这里却输出的是父类的private f()。 这是因为这个private的f方法对子类是屏蔽的,所以子类中这个f()方法其实是个全新的方法,并没有重写父类的那个f()方法。然后这里没有了动态绑定,又用的是父类引用来调用,所以肯定用的是父类中的private的f()。
因此,在子类中,对于基类的private方法,最好采用不同的名字。
2.只有普通方法的调用是多态的动态绑定机制,域和静态方法不是:
来看个例子:
//: polymorphism/FieldAccess.java class Super { public int field = 0; public int getField() { return field; } } class Sub extends Super { public int field = 1; public int getField() { return field; } public int getSuperField() { return super.field; } } public class FieldAccess { public static void main(String[] args) { Super sup = new Sub(); // Upcast System.out.println("sup.field = " + sup.field + ", sup.getField() = " + sup.getField()); Sub sub = new Sub(); System.out.println("sub.field = " + sub.field + ", sub.getField() = " + sub.getField() + ", sub.getSuperField() = " + sub.getSuperField()); } } /* Output: sup.field = 0, sup.getField() = 1 sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0 *///:~
这里看到,所有直接访问field 的操作,都没了动态绑定的效果。
Sub对象在转型为Super引用的时候,任何域的访问都将由编译器解析,因此不是多态的。
在本例中,为Super.field和Sub.field分配了不同的存储空间,这样Sub实际包含了两个叫做field的域。 第一个父类引用的访问field,由于是父类引用,这里又没有了动态绑定,所以就访问的是父类中的field。 第二个子类引用指向子类对象,由于有两个field,而这里没有直接指明super,所以访问的是子类中的field。
3.静态方法也不具有多态性:
//: polymorphism/StaticPolymorphism.java class StaticSuper { public static String staticGet() { return "Base staticGet()"; } public String dynamicGet() { return "Base dynamicGet()"; } } class StaticSub extends StaticSuper { public static String staticGet() { return "Derived staticGet()"; } public String dynamicGet() { return "Derived dynamicGet()"; } } public class StaticPolymorphism { public static void main(String[] args) { StaticSuper sup = new StaticSub(); // Upcast System.out.println(sup.staticGet()); System.out.println(sup.dynamicGet()); } } /* Output: Base staticGet() Derived dynamicGet() *///
这个很容易理解,因为静态的方法是与类相关,而不是与某个对象相关。