Java中的類型擦除與橋方法

類型擦除

Java在語法中雖然存在泛型的概念,可是在虛擬機中卻沒有泛型的概念,虛擬機中全部的類型都是普通類。不管什麼時候定義一個泛型類型,編譯後類型會被都被自動轉換成一個相應的原始類型。java

好比這個類程序員

public class Parent<T> {
    public void sayHello(T value) {
        System.out.println("This is Parent Class, value is " + value);
    }
}

在編譯後就變成了markdown

public class Parent {
    public void sayHello(Object value) {
        System.out.println("This is Parent Class, value is " + value);
    }
}

對類型變量進行替換的規則有兩條:函數

  • 若爲無限定的類型,如<T>,被替換爲Object
  • 若爲限定類型,如<T extends Comparable & Serializable>,則用第一個限定的類型變量來替換,在這裏被替換爲Comparable

橋方法

類型擦除後,就產生了一個奇怪的現象。post

假設有一個超類:測試

public class Parent<T> {
    public void sayHello(T value) {
        System.out.println("This is Parent Class, value is " + value);
    }
}

以及一個子類:this

public class Child extends Parent<String> {
    public void sayHello(String value) {
        System.out.println("This is Child class, value is " + value);
    }
}

最後有如下測試代碼,企圖實現多態:spa

public class MainApp {
    public static void main(String[] args) {
        Child child = new Child();
        Parent<String> parent = child;

        parent.sayHello("This is a string");
    }
}

運行的時候,會對Child類的方法表進行搜索,先分析一下Child類的方法表裏有哪些東西:code

1. sayHello(Object value) : 從類型被擦除後的超類中繼承過來
2. sayHello(String value) : 本身新增的方法,和超類毫無聯繫
3. 一些從Object類繼承來的方法,這裏忽略

按理來講,這段測試代碼應該不能經過編譯,由於要實現多態的話,所調用的方法必須在子類中重寫,可是在這裏Child類並無重寫Parent類中的sayHello(Object value)方法,只是單純的繼承而已,而且新加了一個參數不一樣的同名方法。對象

【本身補充理解】

  • sayHello(Object value)和sayHello(String value)是兩個徹底不一樣的簽名方法,沒有任何關係。可是若是沒有sayHello(String)則調用sayHello("this is string")時則會調用sayHello(Object)簽名的方法。
  • 原文中說不能經過編譯,實際上應該是能夠的,只是再也不是多態而已。子類沒有重寫父類方法,指向子類對象的父類引用依然能夠調用父類本身的方法,只是此時不叫多態。

【本身補充理解完畢】

可是結果是能夠正常運行。

緣由是編譯器在Child類中自動生成了一個橋方法

public void sayHello(Object value) {
    sayHello((String) value);
}

能夠看出,這個橋方法實際上就是對超類中sayHello(Obejct)的重寫。這樣作的緣由是,當程序員在子類中寫下如下這段代碼的時候,本意是對超類中的同名方法進行重寫,但由於超類發生了類型擦除,因此實際上並無重寫成功,所以加入了橋方法的機制來避免類型擦除與多態發生衝突。

public class Child extends Parent<String> {
    public void sayHello(String value) {
        System.out.println("This is Child class, value is " + value);
    }
}

橋方法並不須要本身手動生成,一切都是編譯器自動完成的。

橋方法與Geter

一樣的,若是超類中有getter的話,在使用多態的時候也可能發生衝突。假設有超類被類型擦除後存在這樣一個方法:

Obejct getValue()

而後在子類中,程序員想要重寫這個方法,所以新增了一個這樣的方法:

String getValue()

可是正如前面所述,重寫並無起做用,甚至還應該報錯,由於在子類中,根據 函數簽名=方法名+參數 的原則,從超類繼承的方法與新增的方法衝突了。

但實際上這樣的代碼是能夠工做的,緣由在於,JVM是用返回值+方法名+參數的方式來計算函數簽名的,因此編譯器就能夠藉助這一原則來生成一個橋方法。不過這種計算函數簽名的方法僅僅存在於虛擬機中。

相關文章
相關標籤/搜索