深入理解 Java 泛型擦除机制
我們都知道 Java 中的泛型可以在編譯期對類型檢查,避免類型強(qiáng)制轉(zhuǎn)化帶來的問題,保證代碼的健壯性。不同語言對泛型的支持也不一樣,Java 中的泛型類型在編譯期會擦除,下面一個例子可以證明這一點:
public static void main(String[] args) throws Exception {List<String> list = new ArrayList<>();list.add("abc");Class<? extends List> listClass = list.getClass();Method addMethod = listClass.getMethod("add", Object.class);addMethod.invoke(list, 10);System.out.println(list.size());}上面這個例子中定義了一個 String 類型的集合,通過反射獲取 add 方法并調(diào)用該方法添加了一個 int 類型的元素,代碼執(zhí)行過程中并不會出現(xiàn)異常,而且能正常輸出 size = 2。
因為泛型會被擦除,所以很多人都認(rèn)為在代碼運行期間是無法得知泛型參數(shù)類型的。最近重溫 Java 基礎(chǔ)語法的時候,Java Reflection - Generics 在文章開頭認(rèn)為這種結(jié)論不是很準(zhǔn)確,因為通過反射可以獲取到泛型的具體類型,就像下面這樣:
public class GenericsSamples {public static List<String> getStringList() {return new ArrayList<>();}public static void main(String[] args) throws NoSuchMethodException {Method method = GenericsSamples.class.getMethod("getStringList", null);Type returnType = method.getGenericReturnType();if (returnType instanceof ParameterizedType) {ParameterizedType type = (ParameterizedType) returnType;Type[] typeArguments = type.getActualTypeArguments();for (Type typeArgument : typeArguments) {Class<?> typeArgClass = (Class<?>) typeArgument;System.out.println("typeArgClass = " + typeArgClass);}}} }typeArgClass 輸出的類型為 class java.lang.String,為什么編譯期間被擦除的泛型,在代碼運行期間能獲取到呢?原因是為了讓虛擬機(jī)解析泛型類型,在虛擬機(jī)規(guī)范里引入了 LocalVariableTypeTable 屬性,我們可以通過 javap -v class 反編譯方式查看。
public class GenericsDemo {public static void main(String[] args) {List<String> stringList = new ArrayList<>();List<Integer> integerList = new ArrayList<>();System.out.println(stringList.getClass().equals(integerList.getClass()));} }反編譯上面 GenericsDemo 的 class 文件,main 方法編譯信息如下:
從上面的編譯信息可以看出屬性 LocalVariableTypeTable 的 Signature 中保存了泛型的類型信息,因此我們可以通過反射在運行階段獲取到它。到這里可能會有人有個疑問:泛型不是被擦除了嗎,為啥還能保存到 Signature 中,原因是它和我們所說的泛型擦除是兩碼事,下面再來看一個例子:
public class GenericSamples<T> {private T param;public T getParam() { return param; }public void setParam(T param) { this.param = param; }public static void main(String[] args) {GenericSamples<Integer> integerGenericSamples = new GenericSamples<>();integerGenericSamples.setParam(10);System.out.println(integerGenericSamples.getParam());} }下面是 get 與 set 方法反編譯的結(jié)果:
set 與 get 方法通過反編譯后并不會保留類型信息,統(tǒng)一處理成了 Object,這個才是我們通常所說的泛型擦除,由于被編譯器當(dāng)作 Object 類型處理,那么我們就可以通過反射 set 任意類型的參數(shù)。
和 LocalVariableTypeTable 類似的還有一個 LocalVariableTable,不同的地方在于前者的 Signature 相較于后者的 descriptor 保存了泛型信息,關(guān)于這兩個屬性更多的介紹可以查看官方的介紹:4.7.13. The LocalVariableTable Attribute。
參考:
Java Reflection - Generics
深入探索Java泛型的本質(zhì) | 泛型
總結(jié)
以上是生活随笔為你收集整理的深入理解 Java 泛型擦除机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring AOP 源码系列(一)解析
- 下一篇: 1.Java之路(Java语言发展和概述