泛型擦除的原理

zhengbiyu發表於2024-06-08

以下程式的輸出是什麼:

List<String> stringList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
//輸出true System.out.println(stringList.
class == intList.class);

輸出為true,這意味兩個list的class地址都一樣,為同一個位元組碼檔案。

這個試驗也側面反映出一個現象:泛型在執行時就不存在了。事實上也確實如此,那麼是怎麼做到在編譯時能“限制”型別,而在執行時又沒有了“限制”呢?

所謂的型別擦除(type erasure),指的是泛型只在編譯時起作用,在進入JVM之前,泛型會被擦除掉,根據泛型定義的形式而被替換為相應的型別。這也說明了Java的泛型其實是偽泛型。

無界擦除

  當泛型型別被宣告為一個具體的泛型標識,或一個無界萬用字元時,泛型型別將會被替代為Object

上界擦除

  當泛型型別被宣告為一個上界萬用字元時,泛型型別將會被替代為相應上界的型別。如List<? extends Number>,程式並不能夠確定具體的型別,只知道是Number或其子類,所以會擦除為Number型別。

下界擦除

  下界萬用字元的擦除,同無界萬用字元,只能確定下界型別,但是上界型別無法確定,所以只能替換為Object。如List<? extends Integer>,程式並不能夠確定具體的型別,只知道是Number或其子類,

所以會擦除為Object型別。

繞過編譯時泛型型別檢查

List<Integer> list = new ArrayList<>();
list.add(123);        // 正常編譯
list.add("string"); // 編譯報錯【不相容的型別: java.lang.String無法轉換為java.lang.Integer】

基於型別擦除,我們可以利用反射繞過這個限制。

List<Integer> list = new ArrayList<>();
list.add(123);
try {
    // 由於List中的泛型引數沒有設定上界,所以add方法可以add任何Object的子型別引數
    Method method = list.getClass().getDeclaredMethod("add", Object.class);
    method.invoke(list, "string");
    method.invoke(list, true);
    method.invoke(list, 45.6);
} catch (Exception e) {
    e.printStackTrace();
}

相關文章