1. 程式人生 > 實用技巧 >Java 泛型擦除

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泛型型別,只能儲存整形。最後,我們通過arrayList1物件和arrayList2物件的getClass方法獲取它們的類的資訊,最後發現結果為true。說明泛型型別String和Integer都被擦除掉了,只剩下了 原始型別。

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 arrayList=new ArrayList();所建立的陣列列表arrayList中,不能使用add方法新增整形呢?

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程式碼,這樣的程式碼是無法通過編譯器的檢查的,但是虛擬機器卻是允許這樣做的,因為虛擬機器通過引數型別和返回型別來確定一個方法,所以編譯器為了實現泛型的多型允許自己做這個看起來“不合法”的事情,然後交給虛擬器去區別

參考:

Java 泛型,你瞭解型別擦除嗎?

java泛型(二)、泛型的內部原理:型別擦除以及型別擦除帶來的問題