動態分派之方法重寫

​寫在前面

以前瞭解了靜態分派,如今咱們在看一下動態分派(好專業的概念,以前我也不知道),雖然不知道動態分派這樣的專業名詞,可是重寫(Override),確定是用到過的。java

代碼猜結果

public class DynamicTest {
    static abstract class Car{
         protected abstract void driveCar();

    }

    static class Train extends Car{

        @Override
        protected void driveCar() {
            System.out.println("I am driving train");

        }

    }

    static class Bus extends Car{

        @Override
        protected void driveCar() {
            System.out.println("I am driving bus");

        }

    }

    public static void main(String[] args) {
        Car train = new Train();
        Car bus = new Bus();
        train.driveCar();
        bus.driveCar();
    }
}

結果顯示

I am driving train
I am driving bus

這個運行結果,我也猜到了。最終運行的時候是要指向具體的子類對象,可是再深層次的就回答不上來了。ide

分析過程

Car train = new Train();
Car bus = new Bus();

須要搞清楚,代碼運行的時候,train與bus所指向的類型是什麼,無外乎兩種,一種是靜態類型(Car),一種是實際類型(Train或Bus)。經過上面的結果,很明顯,它們指向的是實際類型。再也不根據靜態類型來決定,那也就確定不是編譯期就作好的指定。接下面,反編譯這個類,一探究竟。code

public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #2                  // class com/erayt/DynamicTest$Tra
in
         3: dup
         4: invokespecial #3                  // Method com/erayt/DynamicTest$Tr
ain."<init>":()V
         7: astore_1
         8: new           #4                  // class com/erayt/DynamicTest$Bus

        11: dup
        12: invokespecial #5                  // Method com/erayt/DynamicTest$Bu
s."<init>":()V
        15: astore_2
        16: aload_1
        17: invokevirtual #6                  // Method com/erayt/DynamicTest$Ca
r.driveCar:()V
        20: aload_2
        21: invokevirtual #6                  // Method com/erayt/DynamicTest$Ca
r.driveCar:()V
        24: return
      LineNumberTable:
        line 30: 0
        line 31: 8
        line 32: 16
        line 33: 20
        line 34: 24
}

dup:複製棧頂數值並將複製值壓入棧頂
invokespecial:調用超類構造方法,實例初始化方法,私有方法
astore_1:將棧頂引用型數值存入第一個本地變量
astore_2:將棧頂引用型數值存入第二個本地變量
aload_1:將第二個引用類型本地變量推送至棧頂
aload_2:將第三個引用類型本地變量推送至棧頂對象

invokevirtual:調用實例方法繼承

從反編譯看,0-15行作的事情是創建train和bus的內存空間,調用Train和Bus類型的實例構造器,將這兩個實例引用存放在局部變量表中的第一、2的slot槽中。對應的代碼就是上面的兩句new對象的過程。內存

接下來,16和20兩句分別將剛建立的兩個對象的引用壓到棧頂,這兩個對象是將要執行driveCar()方法的全部者,17和21句是方法調用指令,可是這兩個指令最終執行的目標方法不行,就要說說invokevirtual指令的多態性查找過程了,它在運行的時候究竟是如何解析的。ci

invokevirtual指令的多態性查找過程it

1.找到操做數棧頂的第一個元素所指向的實際類型(上面的16和20兩句分別將剛建立的兩個對象的引用壓到棧頂)編譯

2.若是在這個實際類型中找到與常量中的描述符號和簡單名稱都相符的方法,則進行權限校驗,經過則返回這個方法的直接引用,查找結束,不經過,報錯,返回java.lang.IllegalAccessError異常ast

3.若是在這個實際類型中沒有找到與常量中的描述符號和簡單名稱都相符的方法,就按照繼承關係從上到下依次對該實際類型的各個父類進行第2步的搜索和驗證

4.若是始終沒有找到合適的方法,拋出錯誤java.lang.AbstractMethodError異常

寫在最後

原來這個運行期多態性的查找都是invokevirtual指令在起做用,它第一步就是在運行期肯定接收者的實際類型,上面反編譯的17和21句調用了invokevirtual指令把常量池中的類方法的符號引用解析到了不一樣的直接引用上面(回想一下類的加載過程,加載、驗證、準備、解析、初始化),而從上面反編譯的17句中invokevirtual指令後面的參數,也顯示了這個常量是Car.driveCar()的符號引用。

17: invokevirtual #6   // Method com/erayt/DynamicTest$Car.driveCar:()V

這個過程也是java重寫的本質,運行期根據實際類型肯定方法執行版本的分派過程稱爲動態分派。

相關文章
相關標籤/搜索