日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

高级Java泛型:检索泛型类型参数

發布時間:2023/12/3 java 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 高级Java泛型:检索泛型类型参数 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在JDK5中引入Java泛型之后, Java泛型Swift成為許多Java程序的組成部分。 但是,乍一看似乎很簡單的Java泛型,程序員很快就會迷失此功能。

大多數Java程序員都知道Java編譯器的類型擦除 。 一般來說,類型擦除意味著有關Java類的所有通用類型信息在其源代碼的編譯過程中都會丟失。 這是對Java向后兼容性的致敬:Java類的所有通用變體在正在運行的Java應用程序中共享一個表示。 如果ArrayList <String>的實例必須記住其泛型類型為String類型,則它必須將該信息存儲在其功能描述中的某個位置,以指示例如List.get實際上返回String類型。 (通過功能描述,我指的是在類的所有實例之間共享的屬性。這包括例如方法或字段定義。與功能描述相反,每個實例各自的實例狀態存儲在其對象表示中。 )因此ArrayList <String>實例的功能描述由其類ArrayList.class表示。 但是,由于ArrayList.class實例與其他實例(也可以是ArrayList <Integer>類型)共享,因此這將需要具有ArrayList.class的兩個不同版本。 但是,這種類表示形式的修改對于較舊的JRE來說是無法理解的,因此會破壞Java應用程序的向后兼容性。 因此,以下比較將始終成功:

assert new ArrayList<String>().getClass() == new ArrayList<Integer>().getClass();

由于這樣的比較是在運行時進行的,而該運行時已經擦除了類的通用類型,因此該比較將ArrayList.class == ArrayList.class轉化為瑣碎的事情。 更具體地說,正在運行的應用程序將確定ArrayList.class等于其自身,并返回true,盡管有String.class!= Integer.class。 這是Java與其他編程語言(例如C ++)的主要區別,也是人們普遍抱怨Java的原因。 (從學術上講,C ++實際上并不了解泛型類型。相反, C ++提供了與泛型相似的模板 。)

到目前為止,對于許多開發人員而言,這并不是什么新鮮事物。 但是,與流行的看法相反,有時甚至在運行時有時也可以檢索通用類型信息。 在解釋之前,讓我們看一個例子。 為此,我們定義以下兩個類:

class MyGenericClass<T> { } class MyStringSubClass extends MyGenericClass<String> { }

MyGenericClass具有通用類型T的單個參數。MyStringSubClass擴展了該通用類,并將T = String分配為其類型參數。 結果,Java編譯器能夠在其子類 MyStringSubClass的字節碼中存儲有關超類MyGenericClass的通用參數類型String的信息。 可以在不破壞向后兼容性的情況下實現此修改,因為此信息僅存儲在已編譯類的字節碼的區域中,而舊JRE版本會忽略該信息。 同時,MyStringSubClass的所有實例仍可以共享一個類表示形式,因為為MyStringSubClass的所有實例設置了T = String。

但是,如何獲取存儲在字節碼中的信息呢? Java API提供了Class.getGenericSuperclass方法,該方法可用于接收Type類型的實例。 如果直接超類實際上是通用的,則返回的實例的類型另外為ParameterizedType,并且可以強制轉換為該實例。 (類型不過是標記接口 。實際實例將是內部ParameterizedTypeImpl類的實例,但是您應始終將其強制轉換為該接口。)由于強制轉換為ParameterizedType接口,因此您現在可以調用ParameterizedType.getActualTypeArguments方法檢索再次為Type類型的數組。 泛型超類的任何泛型類型參數將包含在此數組中與類型定義相同的索引處。 任何代表非泛型類的Type實例都只是Java類類的實現。 (假設您沒有處理返回類型為GenericArrayType的數組。為簡單起見,我將在本文中跳過此方案。)

現在,我們可以利用這些知識來編寫實用程序函數:

public static Class<?> findSuperClassParameterType(Object instance, Class<?> classOfInterest, int parameterIndex) {Class<?> subClass = instance.getClass();while (subClass != subClass.getSuperclass()) {// instance.getClass() is no subclass of classOfInterest or instance is a direct instance of classOfInterestsubClass = subClass.getSuperclass();if (subClass == null) throw new IllegalArgumentException();}ParameterizedType parameterizedType = (ParameterizedType) subClass.getGenericSuperclass();return (Class<?>) parameterizedType.getActualTypeArguments()[parameterIndex]; }

該函數將瀏覽實例的類層次結構,直到將classOfInterest識別為層次結構中的下一個直接子類。 在這種情況下,將使用Class.getGenericSuperclass方法檢索此超類。 如上所述,此方法以包裝表示形式(ParamererizedType)返回類的超類,該表示形式包含在子類中找到的泛型類型。 這使我們能夠成功運行以下應用程序:

Class<?> genericType = findSuperClassParameterType(new MyStringSubClass(), MyGenericClass.class, 0); assert genericType == String.class;

但是要注意

findSuperClassParamerterType(new MyGenericClass<String>(), MyGenericClass.class, 0)

在此實現中將引發異常。 如前所述:通用信息只能在子類的幫助下進行檢索。 但是,MyGenericClass <String>不是MyGenericClass.class的子類,而是具有泛型參數的直接實例。 但是沒有顯式子類,就沒有<something> .class表示形式來存儲String參數。 因此,這次,在編譯過程中無法刪除通用類型。 因此,如果打算對類執行此類查詢,則最好將MyGenericClass定義為抽象。

但是,由于到目前為止存在許多陷阱,我們仍未解決問題。 為了說明原因,請考慮以下類層次結構:

class MyGenericClass<T> { } class MyGenericSubClass<U> extends MyGenericClass<U> class MyStringSubSubClass extends MyGenericSubClass<String> { }

如果我們現在打電話

findSuperClassParameterType(new MyStringSubClass(), MyGenericClass.class, 0);

將會引發異常。 但是為什么會這樣呢? 到目前為止,我們假定MyGenericClass的類型參數T存儲在直接子類中。 在我們的第一個示例中,這是MyStringSubClass,它映射了通用參數T = String。 相反,現在MyStringSubSubClass存儲引用U = String,而MyGenericSubClass只知道U =T。但是,U不是實際類,而是Java類型TypeVariable的類型變量。 如果要解析此層次結構,則必須解析所有這些依賴項。 這可以通過調整示例代碼來實現:

public static Class<?> findSubClassParameterType(Object instance, Class<?> classOfInterest, int parameterIndex) {Map<Type, Type> typeMap = new HashMap<Type, Type>();Class<?> instanceClass = instance.getClass();while (classOfInterest != instanceClass.getSuperclass()) {extractTypeArguments(typeMap, instanceClass);instanceClass = instanceClass.getSuperclass();if (instanceClass == null) throw new IllegalArgumentException();}ParameterizedType parameterizedType = (ParameterizedType) instanceClass.getGenericSuperclass();Type actualType = parameterizedType.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<Type, Type> typeMap, Class<?> clazz) {Type genericSuperclass = clazz.getGenericSuperclass();if (!(genericSuperclass instanceof ParameterizedType)) {return;}ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;Type[] typeParameter = ((Class<?>) parameterizedType.getRawType()).getTypeParameters();Type[] actualTypeArgument = parameterizedType.getActualTypeArguments();for (int i = 0; i < typeParameter.length; i++) {if(typeMap.containsKey(actualTypeArgument[i])) {actualTypeArgument[i] = typeMap.get(actualTypeArgument[i]);}typeMap.put(typeParameter[i], actualTypeArgument[i]);} }

上面的代碼將通過在映射中跟蹤它們來解析任何鏈接的泛型類型定義。 請注意,由于MyClass <A,B>擴展了MyOtherClass <B,A>定義了完全合法的子類型,因此僅按特定索引檢查所有類型定義是不夠的。

但是,我們仍然沒有完成。 再次,我們將首先看一個示例:

class MyGenericOuterClass<U> {public class MyGenericInnerClass<U> { } } class MyStringOuterSubClass extends MyGenericOuterClass<String> { }MyStringOuterSubClass.MyGenericInnerClass inner = new MyStringOuterSubClass().new MyGenericInnerClass();

這次通過調用對內部類的反思

findSuperClassParameterType(inner, MyGenericInnerClass.class, 0);

將失敗。 乍一看,這似乎是必然的結果。 我們正在同一類的實例上的MyGenericInnerClass中尋找通用參數類型。 如上所述,由于無法在MyGenericInnerClass.class中存儲任何泛型類型信息,因此這通常是不可能的。 但是,在這里,我們檢查了泛型類的子類型的(非靜態)內部類的實例。 MyStringOuterSubClass知道U =字符串。 在考慮MyGenericInnterClass的參數類型時,我們必須考慮到這一點。

現在,這里的事情變得非常棘手。 為了在外部類中找到泛型聲明,我們必須首先掌握該外部類。 這可以通過反射和Java編譯器增加了一個這樣的事實來實現的合成 (這意味著沒有源代碼表示)字段此$ 0到任何內部類。 可以通過調用Class.getDeclaredField(“ this $ 0”)來檢索此字段。 通過獲取包含當前內部類的外部類的實例,我們可以自動訪問其Java類。 現在,我們可以按照上述步驟進行操作,并在封閉的類中掃描通用定義,然后將其添加到地圖中。 但是,MyGenericOuterClass中U的類型變量表示形式將不等于MyGenericInnerClass中U的類型表示形式。 就我們所知,MyGenericInnerClass可以是靜態的,并定義自己的通用變量名稱空間。 因此,任何表示Java API中通用變量的TypeVariable類型都具有genericDeclaration屬性。 如果兩個通用變量在不同的類中定義,則TypeVariable表示形式在它們的定義上是不相等的,即使它們在同一個名稱空間中共享一個名稱,而一個類又是另一個的非靜態內部類。

因此,我們必須執行以下操作:

  • 首先,嘗試在內部類超類層次結構中找到泛型類型。 就像處理非嵌套類一樣。
  • 如果無法解析類型:對于(非靜態)內部類及其所有外部類,請盡可能完整地解析類型變量。 這可以通過相同的extractTypeArguments算法實現,并且對于每個嵌套類基本??上為1 .。 我們可以通過檢查this $ 0字段是否為內部類定義來獲取外部類。
  • 檢查外部類之一是否包含具有相同變量名的通用變量的定義。 在這種情況下,您找到了所需的通用變量的實際類型。
  • 在代碼中,如下所示:

    public static Class<?> findSubClassParameterType(Object instance, Class<?> classOfInterest, int parameterIndex) {Map<Type, Type> typeMap = new HashMap<Type, Type>();Class<?> instanceClass = instance.getClass();while (classOfInterest != instanceClass.getSuperclass()) {extractTypeArguments(typeMap, instanceClass);instanceClass = instanceClass.getSuperclass();if (instanceClass == null) throw new IllegalArgumentException();}ParameterizedType parameterizedType = (ParameterizedType) instanceClass.getGenericSuperclass();Type actualType = parameterizedType.getActualTypeArguments()[parameterIndex];if (typeMap.containsKey(actualType)) {actualType = typeMap.get(actualType);}if (actualType instanceof Class) {return (Class<?>) actualType;} else if (actualType instanceof TypeVariable) {return browseNestedTypes(instance, (TypeVariable<?>) actualType);} else {throw new IllegalArgumentException();} }private static Class<?> browseNestedTypes(Object instance, TypeVariable<?> actualType) {Class<?> instanceClass = instance.getClass();List<Class<?>> nestedOuterTypes = new LinkedList<Class<?>>();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<Type, Type> outerTypeMap = new HashMap<Type, Type>();extractTypeArguments(outerTypeMap, outerClass);for (Map.Entry<Type, Type> 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) { /* 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; }

    哇,真丑! 但是上面的代碼使findSubClassParameterType甚至可以用于嵌套類。 我們可以進一步詳細介紹,因為我們還可以找到通用接口,通用方法,字段或數組的類型。 但是,所有此類提取的想法都相同。 如果子類知道其父類的泛型參數,則可以通過反射來獲取它們。 否則,由于類型擦除,泛型參數將在運行時丟失而無法挽回。

    但是最后,這有什么好處? 對于許多開發人員而言,這傳達了執行黑魔法的印象,因此他們寧愿避免編寫此類代碼。 誠然,通常有更簡單的方法來執行這種查詢。 我們可以這樣定義MyGenericSubclass:

    class MyGenericClass<T> {private final Class<T> clazz;public MyGenericClass(Class<T> clazz) {this.clazz = clazz;}public Class<T> getGenericClass() {return clazz;} }

    當然,這也很好,甚至是更少的代碼。 但是,當您編寫供其他開發人員使用的API時,您通常希望它們盡可能的小巧和簡單。 (這可以從編寫一個大型框架到以兩個為一組的形式編寫軟件。)通過上述實現,您迫使班級用戶提供可以通過不同方式檢索的冗余信息。 另外,這種方法對于接口(其中您隱式地要求實現類添加相應的構造函數)不太可能有效。 當著眼于Java 8及其功能接口 (也稱為閉包或lambda表達式)時,此問題將變得更加重要。 如果您需要通用接口除了提供其功能方法之外還提供getGenericClass方法,則不能再在lambda表達式中使用它們。

    PS:在寫這篇博客文章時,我破解了此代碼,但從未真正測試過,而是通過dupa調試 。 如果您需要這種功能,那么可以使用一個出色的名為gentyref的庫來提供上述分析以及更多內容。

    參考: 高級Java泛型:在My My Java Java博客上,從我們的JCG合作伙伴 Rafael Winterhalter 檢索泛型類型參數 。

    翻譯自: https://www.javacodegeeks.com/2013/12/advanced-java-generics-retreiving-generic-type-arguments.html

    總結

    以上是生活随笔為你收集整理的高级Java泛型:检索泛型类型参数的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。