Java 泛型擦除
證明泛型擦除
ArrayList<String> arrayList1=new ArrayList<String>();
arrayList1.add("abc");
ArrayList<Integer> arrayList2=new ArrayList<Integer>();
arrayList2.add(123);
System.out.println(arrayList1.getClass()==arrayList2.getClass());
我們定義了兩個ArrayList陣列,不過一個是ArrayList
ArrayList<Integer> arrayList3=new ArrayList<Integer>();
arrayList3.add(1);//這樣呼叫add方法只能儲存整形,因為泛型型別的例項為Integer
arrayList3.getClass().getMethod("add", Object.class).invoke(arrayList3, "asd");
for (int i=0;i<arrayList3.size();i++) {
System.out.println(arrayList3.get(i));
}
在程式中定義了一個ArrayList泛型型別例項化為Integer的物件,如果直接呼叫add方法,那麼只能儲存整形的資料。不過當我們利用反射呼叫add方法的時候,卻可以儲存字串。這說明了Integer泛型例項在編譯之後被擦除了,只保留了 原始型別。
引用檢查與編譯
說型別變數會在編譯的時候擦除掉,那為什麼我們往ArrayList
java編譯器是通過先檢查程式碼中泛型的型別,然後再進行型別擦除,在進行編譯的。
ArrayList<String> arrayList1=new ArrayList(); arrayList1.add("1");//編譯通過 arrayList1.add(1);//編譯錯誤 String str1=arrayList1.get(0);//返回型別就是String ArrayList arrayList2=new ArrayList<String>(); arrayList2.add("1");//編譯通過 arrayList2.add(1);//編譯通過 Object object=arrayList2.get(0);//返回型別就是Object new ArrayList<String>().add("11");//編譯通過 new ArrayList<String>().add(22);//編譯錯誤 String string=new ArrayList<String>().get(0);//返回型別就是String
型別檢查就是針對引用的,會對這個引用呼叫的方法進行型別檢測,而無關它真正引用的物件。
泛型擦除與多型的衝突和解決方法
泛型過載變重寫?
public class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
public class DateInter extends Pair<Date> {
@Override
public void setValue(Date value) {
super.setValue(value);
}
@Override
public Date getValue() {
return super.getValue();
}
}
在這個子類中,我們設定父類的泛型型別為Pair
將父類的泛型型別限定為Date,那麼父類裡面的兩個方法的引數都為Date型別
public Date getValue() {
return value;
}
public void setValue(Date value) {
this.value = value;
}
實際上,型別擦除後,父類的的泛型型別全部變為了原始型別Object,而子類的型別是Date,引數型別不一樣,這如果實在普通的繼承關係中,根本就不會是重寫,而是過載。
本意是將父類中的泛型轉換為Date型別,子類重寫引數型別為Date的兩個方法
實際上,虛擬機器並不能將泛型型別變為Date,只能將型別擦除掉,變為原始型別Object。這樣,我們的本意是進行重寫,實現多型。可是型別擦除後,只能變為了過載。這樣,型別擦除就和多型有了衝突。JVM知道你的本意嗎?知道!!!可是它能直接實現嗎,不能!!!
如果真的不能的話,那我們怎麼去重寫我們想要的Date型別引數的方法
JVM採用了一個特殊的方法,來完成這項功能,那就是橋方法。
JVM橋方法
我們本意重寫setValue和getValue方法的子類,竟然有4個方法,其實不用驚奇,最後的兩個方法,就是編譯器自己生成的橋方法。可以看到橋方法的引數型別都是Object,也就是說,子類中真正覆蓋父類兩個方法的就是這兩個我們看不到的橋方法。而打在我們自己定義的setvalue和getValue方法上面的@Oveerride只不過是假象。而橋方法的內部實現,就只是去呼叫我們自己重寫的那兩個方法。
所以,虛擬機器巧妙的使用了巧方法,來解決了型別擦除和多型的衝突。
虛擬機器相容方法簽名相同
子類中的橋方法 Object getValue()和Date getValue()是同時存在的,可是如果是常規的兩個方法,他們的方法簽名是一樣的,也就是說虛擬機器根本不能分別這兩個方法。如果是我們自己編寫Java程式碼,這樣的程式碼是無法通過編譯器的檢查的,但是虛擬機器卻是允許這樣做的,因為虛擬機器通過引數型別和返回型別來確定一個方法,所以編譯器為了實現泛型的多型允許自己做這個看起來“不合法”的事情,然後交給虛擬器去區別