关于多态的记录

本博客用来记录阅读《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()
*///

这个很容易理解,因为静态的方法是与类相关,而不是与某个对象相关。

原文地址:https://www.cnblogs.com/wangshen31/p/9770072.html