類型擦除
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是用返回值+方法名+參數的方式來計算函數簽名的,因此編譯器就能夠藉助這一原則來生成一個橋方法。不過這種計算函數簽名的方法僅僅存在於虛擬機中。