JAVA動態繫結淺析

philadelphia發表於2019-01-19

動態繫結

動態繫結作為Java多型特性的一個重要實現

有以下三個類:

public class Fruit {
    public static final String TAG = "Fruit";

    public Fruit() {
    }

    public void say() {
        Log.i(TAG, "say: i am fruit: ");
    }
}
public class Apple  extends  Fruit{

    public void say(){
        Log.i(TAG, "say:  i am apple");
    }
}
public class Orange extends Fruit {

    public void say(){
        Log.i(TAG, "say:  i am orange");
    }
}

其中Apple和Orange類都繼承Fruit類。

然後在使用的時候

Fruit fruit = new Apple();
fruit.say();

我們都知道最後列印的結果是

debug I/Fruit: say:  i am apple

很明顯這就是Java多型特性的體現。

我們明明呼叫的是Fruit物件的方法。但是執行時卻呼叫了Apple物件的方法,這是怎麼實現的呢?這就涉及到了Java的動態繫結了。

這裡確定兩個概念:編譯時型別與執行時型別

編譯時型別就是指改物件在編譯後的檔案裡的型別也就是該物件宣告時的型別,而執行時型別是指在程式執行時動態指定的型別也就是該物件定義時的型別。如果編譯時型別與執行時型別不一致就會發生執行時動態繫結。

比如上面定義的Fruit fruit = new Apple();

fruit的編譯時型別是Fruit。

我們可以從編譯後的class檔案看出

#4 = Class              #243          // com/meiliwu/dragon/model/Apple
#5 = Methodref          #4.#241       // com/meiliwu/dragon/model/Apple."<init>":()V
#6 = Methodref          #244.#245     // com/meiliwu/dragon/model/Fruit.say:()V

我們可以從編譯後的檔案看出。fruit.say方法在編譯後指向的是Fruit的say方法。但是執行時型別是Apple。執行時卻是呼叫Apple的say方法。我們從日誌可以看出來。

如果我們將程式碼改成這樣

Apple fruit = new Apple();
fruit.say();

列印日誌如下:

12-25 11:25:17.803 9709-9709/com.meiliwu.dragon.debug I/Fruit: say:  i am apple

我們再看看編譯後的檔案

4 = Class              #243          // com/meiliwu/dragon/model/Apple
#5 = Methodref          #4.#241       // com/meiliwu/dragon/model/Apple."<init>":()V
#6 = Methodref          #4.#244       // com/meiliwu/dragon/model/Apple.say:()V

從程式碼可以看出編譯時型別與執行時型別一致,這是不會發生動態繫結的,這時可以從編譯後的class檔案得出驗證。

JAVA的PECS原則

PECS指“Producer Extends,Consumer Super”

比如:

List<? extends Fruit> fruitList = new ArrayList<>();
fruitList.add(new Apple());
fruitList.add(new Orange());

但是編譯器卻報了編譯錯誤:

按理說fruitList是一個持有型別為Fruit及其子類的泛型列表啊,為什麼不能往其中新增Fruit的子類呢?

因為泛型的一大好處就是可以在編譯時檢查,避免傳入不相符的型別可能導致的ClassCastException了。但是宣告fruitList的時候沒有明確的指定泛型的具體型別,所以編譯器無法確認其持有的具體型別,當然也就拒絕了add操作。

fruitList只是規定了泛型的上限,但是並沒有確定具體的型別,也無法確定具體的子型別,可以是Apple,Orange還可能是Banana,所以不能把具體的物件新增進去,不然使用的時候可能導致ClassCastException了。但是可以保證從裡面取出來的資料都是Fruit及其子類,而且還是Fruit的某一個子類。

我們把程式碼改成下面這樣子就可以新增不同的Fruit物件了。

因為我們規定了fruitList持有的都是Fruit及其父類,可以將Fruit 及其子類都新增進去

List<? super Fruit> fruitList = new ArrayList<>();
fruitList.add(new Apple());
fruitList.add(new Orange());

但是

fruitList.add(new Object());

卻不行,why?因為在編譯時無法確認具體的fruitList持有的是Fruit的哪一個父類,要想確定就必須指定具體泛型類了。所以就無法往裡面寫入Fruit的父型別物件。

所以我們可以從Java Collctions copy方法的簽名可以看出

public static <T> void copy(List<? super T> dest, List<? extends T> src) {

dest集合持有?super T ,可以往裡面寫入所有T及其子類物件,而src集合持有? extends T泛型。可以確保的是從裡面讀取的資料都是T及其子類。所以可以寫入dest了。

PS:Java 類的final方法和static不能複寫

Reference

1:https://www.cnblogs.com/ygj09…

2: http://www.importnew.com/8966…

相關文章