靜態分派與動態分派——記一次被騰訊面試官暴虐的面試經歷

在騰訊的面試中被問到了重載的運行時多態是怎麼實現的,顯然這一塊是我的知識盲區,所以趕緊補充下。

靜態分派

依賴靜態類型來定位方法執行版本的分派動作稱作靜態分派,靜態分派的典型是方法重載。如下程式碼實例:

public class StaticDispatch {
    static abstract class Human {
    }

    static class Man extends Human {
    }

    static class Woman extends Human {
    }

    public void sayHello (Human guy) {
        System.out.println("hello,guy");
    }

    public void sayHello (Man guy) {
        System.out.println("hello,gentleman");
    }

    public void sayHello (Woman guy) {
        System.out.println("hello,lady");
    }

    public static void main(String[] args) {
        Human man = new Man();
        Human woman = new Woman();
        StaticDispatch sr = new StaticDispatch();
        sr.sayHello(man);//hello,guy!
        sr.sayHello(woman);//hello,guy!
    }
}

對於這段程式碼的運行結果想必大家都很清楚,究其原因,重載的方法調用是由靜態類型而非實際類型決定的,在編譯器便決定了使用哪個重載版本,並會把這個方法的符號引用寫到main()方法的兩條invokevirtual指令的參數中。
總結:Java重載是基於靜態分派完成的。

動態分派

我們知道Java多態性另一種實現方式是「重寫」,而這種運行時多態編譯器是不可能知道它的實際調用的實例的,如下程式碼所示:

Scanner in = new Scanner(System.in);
Person person = null;
if (in.nextLine().equals("chinese")) {
    person = new Chinese();
} else {
    person = new English();
}
person.sayHello();

在重載的場景下,JVM使用的是動態分派,即在運行時確定接受者的實際類型。JVM會在操作數棧中找到指向對象的實際類型,之後在該類的方法表(存在方法區中,存放著各個方法的實際入口)中找到對應描述符和和簡單名稱都相符的方法,同時進行訪問許可權校驗,如通過則查找過程結束。如果沒有找到匹配的方法,則通過繼承關係在該類的父類的方法表中查找,查找到則結束,否則拋異常。

參考資料

多態在 Java 和 C++ 程式語言中的實現比較
《深入理解Java虛擬機》