Java泛型進階 - 如何取出泛型類型參數

在JDK5引入了泛型特性以後,她迅速地成爲Java編程中不可或缺的元素。然而,就跟泛型乍一看彷佛很是容易同樣,許多開發者也很是容易就迷失在這項特性裏。
多數Java開發者都會注意到Java編譯器類型擦除實現方式,Type Erasure會致使關於某個Class的全部泛型信息都會在源代碼編譯時消失掉。在一個Java應用中,能夠認爲全部的泛型實現類,都共享同一個基礎類(注意與繼承區分開來)。這是爲了兼容JDK5以前的全部JDK版本,就是人們常常說的向後兼容性java

向後兼容性

譯者注:原文較爲瑣碎,大體意思是。在JVM整個內存空間中,只會存在一個 ArrayList.class
爲了可以區分 ArrayList<String>ArrayList<Integer>,如今假想的實現方式是在 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<String>().getClass() == new ArrayList<Integer>().getClass();

JVM運行時來講,上述代碼等同於app

assert new ArrayList.class == ArrayList.class

到目前爲止,上述內容都是你們所熟知的事情。然而,與廣泛印象相反的是,某些狀況下在運行時獲取到泛型類型信息也是可行的。舉個栗子:函數

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

MyStringSubClass至關於對MyGenericClass<T>作了類型參數賦值T = String。因而,Java編譯器能夠把這部分泛型信息(父類MyGenericClass的泛型參數是String),存儲在它的子類MyStringSubClass的字節碼區域中。
並且由於這部分泛型信息在被編譯後,僅僅被存儲在被老版JVM所忽略的字節碼區域中,因此這種方式並無破壞向後兼容性。與此同時,由於T已經被賦值爲String,全部的MyStringSubClass類的對象實例仍然共享同一個MyStringSubClass.class工具

如何獲取這塊泛型信息?

應該如何獲取到被存儲在byte code區域的這塊泛型信息呢?this

  1. Java API提供了Class.getGenericSuperClass()方法,來取出一個Type類型的實例
  2. 若是直接父類的實際類型就是泛型類型的話,那取出的Type類型實例就能夠被顯示地轉換爲ParameterizeTypespa

    (Type只是一個標記型接口,它裏面僅包含一個方法: getTypeName()。因此取出的實例的實際類型會是 ParameterizedTypeImpl,但不該直接暴露實際類型,應一直暴露 Type接口)。
  3. 感謝ParameterizedType接口,如今咱們能夠直接調用ParameterizeType.getActualTypeArguments()取出又一個Type類型實例數組
  4. 父類全部的泛型類型參數都會被包含在這個數組裏,而且以被聲明的順序放在數組對應的下標中。
  5. 當數組中的類型參數爲非泛型類型時,咱們就能夠簡單地把它顯示轉換爲Class<?>rest

    爲了保持文章的簡潔性,咱們跳過了 GenericArrayType的狀況。

clipboard.png

如今咱們可使用以上知識編寫一個工具類了:code

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<String>(), MyGenericClass.class, 0)

這樣調用會拋出IllegalArgumentException異常。以前說過:泛型信息只有在子類的幫助下才能被取出。然而,MyGenericClass<String>只是一個擁有泛型參數的類,並非MyGenericClass.class的子類。沒有顯式的子類,就沒有地方存儲String類型參數。所以上述調用不可避免地會被Java編譯器進行類型擦除。若是你已預見到你的項目中會出現這種狀況,也想要避免它,一種良好的編程實踐是將MyGenericClass聲明爲abstract

然而,咱們尚未解決問題,畢竟咱們目前爲止還有許多坑沒有填。

鏈式泛型

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

以下調用,仍然會拋出異常。

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

clipboard.png

這又是爲何呢?到目前爲止咱們都在設想:MyGenericClass的類型參數T的相關信息會存儲在它的直接子類中。那麼上述的類繼承關係就有如下邏輯:

  1. MyStringSubClass.class中存儲了MyGenericSubClass<U> --> U = String
  2. MyGenericSubClass.class中僅存儲了MyGenericClass<T> --> T = U

U並非一個Class類型,而是TypeVariable類型的類型變量,若是咱們想要解析這種繼承關係,就必須解析它們之間全部的依賴關係。代碼以下:

public static Class<?> findSubClassParameterType(Object instance, Class<?> classOfInterest, int parameterIndex) {
    Map<Type, Type> typeMap = 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<Type, Type> 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<A, B> extends 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的泛型信息。就像以前所說,由於MyGenericInnerClass並無子類,因此從MyGenericInnerClass.class中尋找泛型信息是不可能的,畢竟MyGenericInnerClass.class裏面根本就不存在泛型信息。不過在這個例子中,咱們檢查的是MyStringOuterSubClass中的非static內部類: MyGenericInnerClass的對象實例。那麼,MyStringOuterSubClass是知道它的父類MyGennericOuterClass<U> --> 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<U>的U
咱們能夠作如下推理, MyGenericInnerClass是能夠聲明爲 static的,這就意味着 static狀況下, MyGenericInnerClass擁有它本身獨享的 泛型type命名空間。因此,Java API中全部的 TypeVariable接口實現類,都擁有一個屬性叫 genericDeclaration
clipboard.png
clipboard.png
若是兩個 泛型變量被分別定義在不一樣的類中,那麼這兩個 TypeVariable類型變量,從 genericDeclaration的定義上來講就是不相等的。

獲取嵌套類的泛型的代碼以下:

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<>();
            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) {
            /* 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<Type, Type> 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]);
    }
}
相關文章
相關標籤/搜索