以下程式的輸出是什麼:
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(); }