摘要:然而,與普遍印象相反的是,某些情況下在運行時獲取到泛型類型信息也是可行的。于是,編譯器可以把這部分泛型信息父類的泛型參數是,存儲在它的子類的字節碼區域中。當使用反射取出中的類型參數時,就必須把這點納入考量。獲取嵌套類的泛型的代碼如下
在JDK5引入了泛型特性之后,她迅速地成為Java編程中不可或缺的元素。然而,就跟泛型乍一看似乎非常容易一樣,許多開發者也非常容易就迷失在這項特性里。
多數Java開發者都會注意到Java編譯器的類型擦除實現方式,Type Erasure會導致關于某個Class的所有泛型信息都會在源代碼編譯時消失掉。在一個Java應用中,可以認為所有的泛型實現類,都共享同一個基礎類(注意與繼承區分開來)。這是為了兼容JDK5之前的所有JDK版本,就是人們經常說的向后兼容性。
譯者注:原文較為瑣碎,大致意思是。在JVM整個內存空間中,只會存在一個ArrayList.class。那應該怎么做?
為了能夠區分ArrayList和ArrayList ,現在假想的實現方式是在Class文件信息表(函數表+字段表)里添加額外的泛型信息。這個時候JVM的內存空間中就會存在(假設)ArrayList&String.class和(假設)ArrayList&Integer.class文件。順著這種情況延續下去的話,就必須要修改JDK5之前所有版本的JVM對Class文件的識別邏輯,因為它破壞了JVM內部一個Class只對應唯一一個.class這條規則。這也是人們常說的: 破壞了向后兼容性。 注:參考Python3舍棄掉Python2的例子,也是放棄了對2的兼容,Python3才能發展并構造更多的新特性。
既然Java開發團隊選擇了兼容JDK5之前的版本,那就不能在JVM里做手腳了。但Java編譯器的代碼似乎還是可以修改的。于是,Java編譯器在編譯時就會把泛型信息都擦除,所以以下的比較在JVM運行時會永遠為真。
assert new ArrayList().getClass() == new ArrayList ().getClass();
對JVM運行時來說,上述代碼等同于
assert new ArrayList.class == ArrayList.class
到目前為止,上述內容都是大家所熟知的事情。然而,與普遍印象相反的是,某些情況下在運行時獲取到泛型類型信息也是可行的。舉個栗子:
class MyGenericClass{ } class MyStringSubClass extends MyGenericClass { }
MyStringSubClass相當于對MyGenericClass
而且因為這部分泛型信息在被編譯后,僅僅被存儲在被老版JVM所忽略的字節碼區域中,所以這種方式并沒有破壞向后兼容性。與此同時,因為T已經被賦值為String,所有的MyStringSubClass類的對象實例仍然共享同一個MyStringSubClass.class。
應該如何獲取到被存儲在byte code區域的這塊泛型信息呢?
Java API提供了Class.getGenericSuperClass()方法,來取出一個Type類型的實例。
如果直接父類的實際類型就是泛型類型的話,那取出的Type類型實例就可以被顯示地轉換為ParameterizeType。
(Type只是一個標記型接口,它里面僅包含一個方法:getTypeName()。所以取出的實例的實際類型會是ParameterizedTypeImpl,但不應直接暴露實際類型,應一直暴露Type接口)。
感謝ParameterizedType接口,現在我們可以直接調用ParameterizeType.getActualTypeArguments()取出又一個Type類型實例數組。
父類所有的泛型類型參數都會被包含在這個數組里,并且以被聲明的順序放在數組對應的下標中。
當數組中的類型參數為非泛型類型時,我們就可以簡單地把它顯示轉換為Class>。
為了保持文章的簡潔性,我們跳過了GenericArrayType的情況。
現在我們可以使用以上知識編寫一個工具類了:
public static Class> findSuperClassParameterType(Object instance, Class> clazzOfInterest, int parameterIndex) { Class> subClass = instance.getClass(); while (subClass.getSuperclass() != clazzOfInterest) { subClass = subClass.getSuperclass(); if (subClass == null) throw new IllegalArgumentException(); } ParameterizedType pt = (ParameterizedType) (subClass.getGenericSuperclass()); return (Class>) pt.getActualTypeArguments()[parameterIndex]; } public static void testCase1() { Class> genericType = findDirectSuperClassParameterType(new MyStringSubClass()); System.out.println(genericType); assert genericType == String.class; }
然而,請注意到
findSuperClassParamerterType(new MyGenericClass(), MyGenericClass.class, 0)
這樣調用會拋出IllegalArgumentException異常。之前說過:泛型信息只有在子類的幫助下才能被取出。然而,MyGenericClass
然而,我們還沒有解決問題,畢竟我們目前為止還有許多坑沒有填。
鏈式泛型class MyGenericClass{} class MyGenericSubClass extends MyGenericClass {} class MyStringSubSubClass extends MyGenericSubClass {}
如下調用,仍然會拋出異常。
findSuperClassParameterType(new MyStringSubClass(), MyGenericClass.class, 0);
這又是為什么呢?到目前為止我們都在設想:MyGenericClass的類型參數T的相關信息會存儲在它的直接子類中。那么上述的類繼承關系就有以下邏輯:
MyStringSubClass.class中存儲了MyGenericSubClass --> U = String。
MyGenericSubClass.class中僅存儲了MyGenericClass
但U并不是一個Class類型,而是TypeVariable類型的類型變量,如果我們想要解析這種繼承關系,就必須解析它們之間所有的依賴關系。代碼如下:
public static Class> findSubClassParameterType(Object instance, Class> classOfInterest, int parameterIndex) { MaptypeMap = new HashMap<>(); Class> instanceClass = instance.getClass(); while (instanceClass.getSuperclass() != classOfInterest) { extractTypeArguments(typeMap, instanceClass); instanceClass = instanceClass.getSuperclass(); if (instanceClass == null) throw new IllegalArgumentException(); } // System.out.println(typeMap); ParameterizedType pt = (ParameterizedType) instanceClass.getGenericSuperclass(); Type actualType = pt.getActualTypeArguments()[parameterIndex]; if (typeMap.containsKey(actualType)) { actualType = typeMap.get(actualType); } if (actualType instanceof Class) { return (Class>) actualType; } else { throw new IllegalArgumentException(); } } private static void extractTypeArguments(Map typeMap, Class> clazz) { Type genericSuperclass = clazz.getGenericSuperclass(); if (!(genericSuperclass instanceof ParameterizedType)) { return ; } ParameterizedType pt = (ParameterizedType) genericSuperclass; Type[] typeParameters = ((Class>) pt.getRawType()).getTypeParameters(); Type[] actualTypeArguments = pt.getActualTypeArguments(); for (int i = 0; i < typeParameters.length; i++) { if (typeMap.containsKey(actualTypeArguments[i])) { actualTypeArguments[i] = typeMap.get(actualTypeArguments[i]); } typeMap.put(typeParameters[i], actualTypeArguments[i]); } }
代碼中通過一個map可以解析所有鏈式泛型類型的定義。不過仍然不夠完美,畢竟MyClass extends MyOtherClass也是一種完全合法的子類定義。
嵌套類好了好了,仍然沒有結束:
class MyGenericOuterClass { public class MyGenericInnerClass { } } class MyStringOuterSubClass extends MyGenericOuterClass{ } MyStringOuterSubClass.MyGenericInnerClass inner = new MyStringOuterSubClass().new MyGenericInnerClass();
下面這樣調用仍然會失敗。
findSuperClassParameterType(inner, MyGenericInnerClass.class, 0);
這種失敗幾乎是可預見的,我們正試圖在MyGenericInnerClass的對象實例里面尋找MyGenericInnerClass的泛型信息。就像之前所說,因為MyGenericInnerClass并沒有子類,所以從MyGenericInnerClass.class中尋找泛型信息是不可能的,畢竟MyGenericInnerClass.class里面根本就不存在泛型信息。不過在這個例子中,我們檢查的是MyStringOuterSubClass中的非static內部類: MyGenericInnerClass的對象實例。那么,MyStringOuterSubClass是知道它的父類MyGennericOuterClass --> U = String。當使用反射取出MyGenericInnerClass中的類型參數時,就必須把這點納入考量。
現在這件事就變得相當棘手了。
-> 為了取出MyGenericOuterClass的泛型信息
-> 就必須先得到MyGenericOuterClass.class
這依然可以通過反射取得,Java編譯器會在內部類MyGenericInnerClass中生成一個synthetic-field: this$0,這個字段可以通過Class.getDeclaredField("this$0")獲取到。
> javap -p -v MyGenericOuterClass$MyGenericInnerClass.class ... ... final cn.local.test.MyGenericOuterClass this$0; descriptor: Lcn/local/test/MyGenericOuterClass; flags: ACC_FINAL, ACC_SYNTHETIC ...
既然已經有辦法可以獲取到MyGenericOuterClass.class了,那接下來我們似乎可以直接復用之前的掃描邏輯了。
這里需要注意, MyGenericOuterClass的U 并不等同于的U 。
我們可以做以下推理,MyGenericInnerClass是可以聲明為static的,這就意味著static情況下,MyGenericInnerClass擁有它自己獨享的泛型type命名空間。所以,Java API中所有的TypeVariable接口實現類,都擁有一個屬性叫genericDeclaration。
如果兩個泛型變量被分別定義在不同的類中,那么這兩個TypeVariable類型變量,從genericDeclaration的定義上來說就是不相等的。
獲取嵌套類的泛型的代碼如下:
private static Class> browseNestedTypes(Object instance, TypeVariable> actualType) { Class> instanceClass = instance.getClass(); List> nestedOuterTypes = new LinkedList >(); for ( Class> enclosingClass = instanceClass.getEnclosingClass(); enclosingClass != null; enclosingClass = enclosingClass.getEnclosingClass() ) { try { Field this$0 = instanceClass.getDeclaredField("this$0"); Object outerInstance = this$0.get(instance); Class> outerClass = outerInstance.getClass(); nestedOuterTypes.add(outerClass); Map outerTypeMap = new HashMap<>(); extractTypeArguments(outerTypeMap, outerClass); for (Map.Entry entry : outerTypeMap.entrySet()) { if (!(entry.getKey() instanceof TypeVariable)) { continue; } TypeVariable> foundType = (TypeVariable>) entry.getKey(); if (foundType.getName().equals(actualType.getName()) && isInnerClass(foundType.getGenericDeclaration(), actualType.getGenericDeclaration())) { if (entry.getValue() instanceof Class) { return (Class>) entry.getValue(); } actualType = (TypeVariable>) entry.getValue(); } } } catch (NoSuchFieldException e) { /* however, this should never happen. */ } catch (IllegalAccessException e) { /* this might happen */ } } throw new IllegalArgumentException(); } private static boolean isInnerClass(GenericDeclaration outerDeclaration, GenericDeclaration innerDeclaration) { if (!(outerDeclaration instanceof Class) || !(innerDeclaration instanceof Class)) { throw new IllegalArgumentException(); } Class> outerClass = (Class>) outerDeclaration; Class> innerClass = (Class>) innerDeclaration; while ((innerClass = innerClass.getEnclosingClass()) != null) { if (innerClass == outerClass) { return true; } } return false; } private static void extractTypeArguments(Map typeMap, Class> clazz) { Type genericSuperclass = clazz.getGenericSuperclass(); if (!(genericSuperclass instanceof ParameterizedType)) { return; } ParameterizedType pt = (ParameterizedType) genericSuperclass; Type[] typeParameters = ((Class>) pt.getRawType()).getTypeParameters(); Type[] actualTypeArguments = pt.getActualTypeArguments(); for (int i = 0; i < typeParameters.length; i++) { if (typeMap.containsKey(actualTypeArguments[i])) { actualTypeArguments[i] = typeMap.get(actualTypeArguments[i]); } typeMap.put(typeParameters[i], actualTypeArguments[i]); } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/73445.html
博客地址:Java泛型:類型擦除 前情回顧 Java泛型:泛型類、泛型接口和泛型方法 類型擦除 代碼片段一 Class c1 = new ArrayList().getClass(); Class c2 = new ArrayList().getClass(); System.out.println(c1 == c2); /* Output true */ 顯然在平時使用中,ArrayList...
摘要:接口也是集合中的一員,但它與接口有所不同,接口與接口主要用于存儲元素,而主要用于迭代訪問即遍歷中的元素,因此對象也被稱為迭代器。迭代器的實現原理我們在之前案例已經完成了遍歷集合的整個過程。 【Collection、泛型】 主要內容 Collection集合 迭代器 增強for 泛型 教學目標 [ ] 能夠說出集合與數組的區別 [ ] 說出Collection集合的常用功能 [ ]...
摘要:以上代碼編譯通過,運行通過引入泛型的同時,也為了兼容之前的類庫,開始引入的其實是偽泛型,在生成的字節碼中是不包含泛型中的類型信息的。進行類型擦除后,類型參數原始類型就是擦除去了泛型信息,最后在字節碼中的類型變量的真正類型。 Java泛型 Java泛型(generics)是JDK 5中引入的一個新特性,允許在定義類和接口的時候使用類型參數(type parameter)。聲明的類型參數在...
摘要:集合框架重點理解用于存儲數據的容器。集合容器在不斷向上抽取過程中。出現了集合體系。,刪除將集合中的元素全刪除,清空集合。刪除集合中指定的對象。注意刪除成功,集合的長度會改變。作用用于取集合中的元素。是集合特有的迭代器。是單列集合是雙列集合 集合框架(重點理解):用于存儲數據的容器。特點:1:對象封裝數據,對象多了也需要存儲。集合用于存儲對象。2:對象的個數確定可以使用數組,但是不確定怎...
閱讀 1160·2023-04-26 03:02
閱讀 1196·2023-04-25 19:18
閱讀 2596·2021-11-23 09:51
閱讀 2579·2021-11-11 16:55
閱讀 2634·2021-10-21 09:39
閱讀 1711·2021-10-09 09:59
閱讀 2007·2021-09-26 09:55
閱讀 3535·2021-09-26 09:55