摘要:然而,與普遍印象相反的是,某些情況下在運(yùn)行時(shí)獲取到泛型類型信息也是可行的。于是,編譯器可以把這部分泛型信息父類的泛型參數(shù)是,存儲(chǔ)在它的子類的字節(jié)碼區(qū)域中。當(dāng)使用反射取出中的類型參數(shù)時(shí),就必須把這點(diǎn)納入考量。獲取嵌套類的泛型的代碼如下
在JDK5引入了泛型特性之后,她迅速地成為Java編程中不可或缺的元素。然而,就跟泛型乍一看似乎非常容易一樣,許多開(kāi)發(fā)者也非常容易就迷失在這項(xiàng)特性里。
多數(shù)Java開(kāi)發(fā)者都會(huì)注意到Java編譯器的類型擦除實(shí)現(xiàn)方式,Type Erasure會(huì)導(dǎo)致關(guān)于某個(gè)Class的所有泛型信息都會(huì)在源代碼編譯時(shí)消失掉。在一個(gè)Java應(yīng)用中,可以認(rèn)為所有的泛型實(shí)現(xiàn)類,都共享同一個(gè)基礎(chǔ)類(注意與繼承區(qū)分開(kāi)來(lái))。這是為了兼容JDK5之前的所有JDK版本,就是人們經(jīng)常說(shuō)的向后兼容性。
譯者注:原文較為瑣碎,大致意思是。在JVM整個(gè)內(nèi)存空間中,只會(huì)存在一個(gè)ArrayList.class。那應(yīng)該怎么做?
為了能夠區(qū)分ArrayList和ArrayList ,現(xiàn)在假想的實(shí)現(xiàn)方式是在Class文件信息表(函數(shù)表+字段表)里添加額外的泛型信息。這個(gè)時(shí)候JVM的內(nèi)存空間中就會(huì)存在(假設(shè))ArrayList&String.class和(假設(shè))ArrayList&Integer.class文件。順著這種情況延續(xù)下去的話,就必須要修改JDK5之前所有版本的JVM對(duì)Class文件的識(shí)別邏輯,因?yàn)樗茐牧?b>JVM內(nèi)部一個(gè)Class只對(duì)應(yīng)唯一一個(gè).class這條規(guī)則。這也是人們常說(shuō)的: 破壞了向后兼容性。 注:參考Python3舍棄掉Python2的例子,也是放棄了對(duì)2的兼容,Python3才能發(fā)展并構(gòu)造更多的新特性。
既然Java開(kāi)發(fā)團(tuán)隊(duì)選擇了兼容JDK5之前的版本,那就不能在JVM里做手腳了。但Java編譯器的代碼似乎還是可以修改的。于是,Java編譯器在編譯時(shí)就會(huì)把泛型信息都擦除,所以以下的比較在JVM運(yùn)行時(shí)會(huì)永遠(yuǎn)為真。
assert new ArrayList().getClass() == new ArrayList ().getClass();
對(duì)JVM運(yùn)行時(shí)來(lái)說(shuō),上述代碼等同于
assert new ArrayList.class == ArrayList.class
到目前為止,上述內(nèi)容都是大家所熟知的事情。然而,與普遍印象相反的是,某些情況下在運(yùn)行時(shí)獲取到泛型類型信息也是可行的。舉個(gè)栗子:
class MyGenericClass{ } class MyStringSubClass extends MyGenericClass { }
MyStringSubClass相當(dāng)于對(duì)MyGenericClass
而且因?yàn)檫@部分泛型信息在被編譯后,僅僅被存儲(chǔ)在被老版JVM所忽略的字節(jié)碼區(qū)域中,所以這種方式并沒(méi)有破壞向后兼容性。與此同時(shí),因?yàn)?strong>T已經(jīng)被賦值為String,所有的MyStringSubClass類的對(duì)象實(shí)例仍然共享同一個(gè)MyStringSubClass.class。
應(yīng)該如何獲取到被存儲(chǔ)在byte code區(qū)域的這塊泛型信息呢?
Java API提供了Class.getGenericSuperClass()方法,來(lái)取出一個(gè)Type類型的實(shí)例。
如果直接父類的實(shí)際類型就是泛型類型的話,那取出的Type類型實(shí)例就可以被顯示地轉(zhuǎn)換為ParameterizeType。
(Type只是一個(gè)標(biāo)記型接口,它里面僅包含一個(gè)方法:getTypeName()。所以取出的實(shí)例的實(shí)際類型會(huì)是ParameterizedTypeImpl,但不應(yīng)直接暴露實(shí)際類型,應(yīng)一直暴露Type接口)。
感謝ParameterizedType接口,現(xiàn)在我們可以直接調(diào)用ParameterizeType.getActualTypeArguments()取出又一個(gè)Type類型實(shí)例數(shù)組。
父類所有的泛型類型參數(shù)都會(huì)被包含在這個(gè)數(shù)組里,并且以被聲明的順序放在數(shù)組對(duì)應(yīng)的下標(biāo)中。
當(dāng)數(shù)組中的類型參數(shù)為非泛型類型時(shí),我們就可以簡(jiǎn)單地把它顯示轉(zhuǎn)換為Class>。
為了保持文章的簡(jiǎn)潔性,我們跳過(guò)了GenericArrayType的情況。
現(xiàn)在我們可以使用以上知識(shí)編寫(xiě)一個(gè)工具類了:
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; }
然而,請(qǐng)注意到
findSuperClassParamerterType(new MyGenericClass(), MyGenericClass.class, 0)
這樣調(diào)用會(huì)拋出IllegalArgumentException異常。之前說(shuō)過(guò):泛型信息只有在子類的幫助下才能被取出。然而,MyGenericClass
然而,我們還沒(méi)有解決問(wèn)題,畢竟我們目前為止還有許多坑沒(méi)有填。
鏈?zhǔn)椒盒?/b>class MyGenericClass{} class MyGenericSubClass extends MyGenericClass {} class MyStringSubSubClass extends MyGenericSubClass {}
如下調(diào)用,仍然會(huì)拋出異常。
findSuperClassParameterType(new MyStringSubClass(), MyGenericClass.class, 0);
這又是為什么呢?到目前為止我們都在設(shè)想:MyGenericClass的類型參數(shù)T的相關(guān)信息會(huì)存儲(chǔ)在它的直接子類中。那么上述的類繼承關(guān)系就有以下邏輯:
MyStringSubClass.class中存儲(chǔ)了MyGenericSubClass --> U = String。
MyGenericSubClass.class中僅存儲(chǔ)了MyGenericClass
但U并不是一個(gè)Class類型,而是TypeVariable類型的類型變量,如果我們想要解析這種繼承關(guān)系,就必須解析它們之間所有的依賴關(guān)系。代碼如下:
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]); } }
代碼中通過(guò)一個(gè)map可以解析所有鏈?zhǔn)椒盒皖愋?/b>的定義。不過(guò)仍然不夠完美,畢竟MyClass extends MyOtherClass也是一種完全合法的子類定義。
嵌套類好了好了,仍然沒(méi)有結(jié)束:
class MyGenericOuterClass { public class MyGenericInnerClass { } } class MyStringOuterSubClass extends MyGenericOuterClass{ } MyStringOuterSubClass.MyGenericInnerClass inner = new MyStringOuterSubClass().new MyGenericInnerClass();
下面這樣調(diào)用仍然會(huì)失敗。
findSuperClassParameterType(inner, MyGenericInnerClass.class, 0);
這種失敗幾乎是可預(yù)見(jiàn)的,我們正試圖在MyGenericInnerClass的對(duì)象實(shí)例里面尋找MyGenericInnerClass的泛型信息。就像之前所說(shuō),因?yàn)?b>MyGenericInnerClass并沒(méi)有子類,所以從MyGenericInnerClass.class中尋找泛型信息是不可能的,畢竟MyGenericInnerClass.class里面根本就不存在泛型信息。不過(guò)在這個(gè)例子中,我們檢查的是MyStringOuterSubClass中的非static內(nèi)部類: MyGenericInnerClass的對(duì)象實(shí)例。那么,MyStringOuterSubClass是知道它的父類MyGennericOuterClass --> U = String。當(dāng)使用反射取出MyGenericInnerClass中的類型參數(shù)時(shí),就必須把這點(diǎn)納入考量。
現(xiàn)在這件事就變得相當(dāng)棘手了。
-> 為了取出MyGenericOuterClass的泛型信息
-> 就必須先得到MyGenericOuterClass.class
這依然可以通過(guò)反射取得,Java編譯器會(huì)在內(nèi)部類MyGenericInnerClass中生成一個(gè)synthetic-field: this$0,這個(gè)字段可以通過(guò)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 ...
既然已經(jīng)有辦法可以獲取到MyGenericOuterClass.class了,那接下來(lái)我們似乎可以直接復(fù)用之前的掃描邏輯了。
這里需要注意, MyGenericOuterClass的U 并不等同于的U 。
我們可以做以下推理,MyGenericInnerClass是可以聲明為static的,這就意味著static情況下,MyGenericInnerClass擁有它自己獨(dú)享的泛型type命名空間。所以,Java API中所有的TypeVariable接口實(shí)現(xiàn)類,都擁有一個(gè)屬性叫genericDeclaration。
如果兩個(gè)泛型變量被分別定義在不同的類中,那么這兩個(gè)TypeVariable類型變量,從genericDeclaration的定義上來(lái)說(shuō)就是不相等的。
獲取嵌套類的泛型的代碼如下:
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]); } }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/73445.html
博客地址:Java泛型:類型擦除 前情回顧 Java泛型:泛型類、泛型接口和泛型方法 類型擦除 代碼片段一 Class c1 = new ArrayList().getClass(); Class c2 = new ArrayList().getClass(); System.out.println(c1 == c2); /* Output true */ 顯然在平時(shí)使用中,ArrayList...
摘要:接口也是集合中的一員,但它與接口有所不同,接口與接口主要用于存儲(chǔ)元素,而主要用于迭代訪問(wèn)即遍歷中的元素,因此對(duì)象也被稱為迭代器。迭代器的實(shí)現(xiàn)原理我們?cè)谥鞍咐呀?jīng)完成了遍歷集合的整個(gè)過(guò)程。 【Collection、泛型】 主要內(nèi)容 Collection集合 迭代器 增強(qiáng)for 泛型 教學(xué)目標(biāo) [ ] 能夠說(shuō)出集合與數(shù)組的區(qū)別 [ ] 說(shuō)出Collection集合的常用功能 [ ]...
摘要:以上代碼編譯通過(guò),運(yùn)行通過(guò)引入泛型的同時(shí),也為了兼容之前的類庫(kù),開(kāi)始引入的其實(shí)是偽泛型,在生成的字節(jié)碼中是不包含泛型中的類型信息的。進(jìn)行類型擦除后,類型參數(shù)原始類型就是擦除去了泛型信息,最后在字節(jié)碼中的類型變量的真正類型。 Java泛型 Java泛型(generics)是JDK 5中引入的一個(gè)新特性,允許在定義類和接口的時(shí)候使用類型參數(shù)(type parameter)。聲明的類型參數(shù)在...
摘要:集合框架重點(diǎn)理解用于存儲(chǔ)數(shù)據(jù)的容器。集合容器在不斷向上抽取過(guò)程中。出現(xiàn)了集合體系。,刪除將集合中的元素全刪除,清空集合。刪除集合中指定的對(duì)象。注意刪除成功,集合的長(zhǎng)度會(huì)改變。作用用于取集合中的元素。是集合特有的迭代器。是單列集合是雙列集合 集合框架(重點(diǎn)理解):用于存儲(chǔ)數(shù)據(jù)的容器。特點(diǎn):1:對(duì)象封裝數(shù)據(jù),對(duì)象多了也需要存儲(chǔ)。集合用于存儲(chǔ)對(duì)象。2:對(duì)象的個(gè)數(shù)確定可以使用數(shù)組,但是不確定怎...
摘要:泛型類泛型類和普通類的區(qū)別就是類定義時(shí),在類名后加上泛型聲明。泛型類的內(nèi)部成員方法就可以使用聲明的參數(shù)類型。 泛型是JDK 1.5的一項(xiàng)新特性,它的本質(zhì)是參數(shù)化類型(Parameterized Type),即所操作的數(shù)據(jù)類型在定義時(shí)被指定為一個(gè)參數(shù)。當(dāng)我們使用的時(shí)候給這個(gè)參數(shù)指定不同的對(duì)象類型,就可以處理不同的對(duì)象。這種參數(shù)類型可以用在類、接口和方法的創(chuàng)建中,分別稱為泛型類、泛型接口和...
閱讀 1117·2023-04-26 03:02
閱讀 1161·2023-04-25 19:18
閱讀 2583·2021-11-23 09:51
閱讀 2561·2021-11-11 16:55
閱讀 2614·2021-10-21 09:39
閱讀 1694·2021-10-09 09:59
閱讀 1991·2021-09-26 09:55
閱讀 3512·2021-09-26 09:55