我所理解的JDK泛型(二):反射與泛型擦除

前篇的文章中也提到了,泛型是幫助開發者避免程序運行時出現異常轉換類型錯誤而設計的,使用在開發階段。編譯時編譯器會進行泛型擦除,運行期經過反射獲取不到運行時的實際泛型類型信息。java

  那麼,這句話到底對仍是不對呢?什麼狀況下能夠反射到泛型的類型信息,什麼狀況下不能呢?this

首先,明確一個問題。設計

泛型中的標記符(T、E、K、V等)與泛型的通配符(?)有什麼區別的?code

泛型的標記符出如今泛型類或泛型方法的定義中。
泛型的通配符出如今泛型類或泛型方法的使用中。對象

好比,要定義一個MyArrayList.class。這個類要具有泛型的功能,因而MyArrayList<T>.class就定義了泛型。
好比,在某個方法或屬性或子類中要使用MyArrayList<T>.class這個類,就能夠使用通配符了 private list = new MyArrayList<?>();繼承

那麼List<? extends T>用於什麼地方呢?固然是使用在泛型的定義中。接口

接下來,回到正題。定義一個Box<T>:開發

public class Box<T> {
    private T t;

    public void set(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }
}

在使用的代碼中嘗試經過反射get()獲取方法返回值的泛型類型信息:get

public void test() {
        Box<Integer> box = new Box<>();
        
        Method method = box.getClass().getMethod("get", null);

        Type genericReturnType = method.getGenericReturnType();

        System.out.println(genericReturnType instanceof ParameterizedType);

        if (genericReturnType instanceof ParameterizedType){
            System.out.println(genericReturnType.getTypeName());
        }	
    }

運行代碼,控制檯輸出 "false" ,這明顯不是咱們想要獲得的 "java.lang.Integer"。編譯器

修改Box<T>的代碼爲:

public class Box<T extends Number> {
    
}

再次運行代碼,控制檯依然輸出 "false" 。

這兩次的改動說明編譯器進行了泛型擦除,反射得不到泛型的類型信息。

那麼,什麼狀況下反射能獲得泛型的類型信息呢
修改Box<T>的代碼:

public class MyBox extends Box<Number>{}

  在使用的代碼中嘗試經過反射MyBox.class獲得父類Box.class泛型類型信息<Number>:

public void test() throws Exception {
        Type genericSuperclass = MyBox.class.getGenericSuperclass();
        
        System.out.println(genericSuperclass instanceof ParameterizedType);

        if (genericSuperclass instanceof ParameterizedType) {
            Type[] actualTypeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments();
            for (Type type : actualTypeArguments) {
                System.out.println(type.getTypeName());
            }
        }

    }

運行代碼,控制檯輸出"true"和"java.lang.Number"。
說明成功獲取到了父類Box.class泛型類型信息<Number>。

若是反射MyBox.class獲得從父類Box繼承來的的方法get(),能不能獲取到泛型類型信息呢?
讀者能夠自行嘗試一下,答案是否認的。

回到本文的入題,什麼狀況下能夠反射到泛型的類型信息,什麼狀況下不能呢?

我的理解:在編譯器已經明確了泛型的具體類型信息,或者使用通配符明確了泛型類型的上限活者下限的具體類型類型信息,則是能夠經過反射獲取到該泛型的具體類型信息的
換句話說:在定義泛型的類中是不能反射獲得具體的泛型類型信息的,在使用泛型的類中是能夠的。
(先看明白本文開篇所提到的定義泛型使用泛型的概念)

在具體的使用上,直接上代碼:

public class MyBox extends Box<Number> { // 父類有具體的泛型信息
    public List<String> list;  // 屬性有具體的泛型信息
    public List<Long> get(List<Integer> list){  // 方法的參數有具體的泛型信息,方法的返回值有具體的泛型信息
        return new ArrayList<Long>();
    }
public static void main(String[] args) throws Exception {
        MyBox myBox = new MyBox();
        myBox.testReflectSuperClass(); // 獲取父類的泛型信息
        myBox.testReflectField(); // 獲取屬性的泛型信息
        myBox.testReflectMethodReturnType(); // 獲取方法返回值的泛型信息
        myBox.testReflectMethodParameterType(); // 獲取方法參數的泛型信息
    }
    
    public void testReflectSuperClass(){
        Type genericSuperclass = MyBox.class.getGenericSuperclass();
        test(genericSuperclass);
    }
    
    public void testReflectField() throws Exception {
        Type genericType = MyBox.class.getField("list").getGenericType();
        test(genericType);
    }
    
    public void testReflectMethodReturnType() throws Exception {
        Type genericReturnType = MyBox.class.getMethod("get", List.class).getGenericReturnType();
        test(genericReturnType);
    }
    
    public void testReflectMethodParameterType() throws Exception {
        Type[] genericParameterTypes = MyBox.class.getMethod("get", List.class).getGenericParameterTypes();
        for (Type type : genericParameterTypes) {
            test(type);
        }
    }
    
    public void test(Type genericType){
        if (genericType instanceof ParameterizedType) {
            Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
            for (Type type : actualTypeArguments) {
                System.out.println(type.getTypeName());
            }
        }
    }
}

運行代碼,控制分別輸出

java.lang.Number
java.lang.String
java.lang.Long
java.lang.Integer

說明經過反射獲取到了運行期的泛型的具體的類型信息。

總結:
1,在定義泛型的類中使用的是標記符,編譯後的class中沒有泛型信息;
2,在定義泛型的類中使用了<? extends T>,屬於未標明具體類型信息,編譯後的class中沒有泛型信息;
3,在使用泛型的類中指定了具體的泛型信息<String>,編譯後的class中包含泛型信息<String>;
4,在使用泛型的類中指定了具體的泛型信息<? extends Number>,編譯後的class中包含泛型信息<? extends Number>;
5,能夠獲取泛型的具體類型信息包括:

  • 父類或父接口的泛型信息;
  • 屬性的泛型信息;
  • 方法參數的泛型信息;
  • 方法返回值的泛型信息。

備註
1,Type是Class、Field、Method等的父接口。若是判斷Type屬於Class類型,則能夠使用Class clazz = (Class)Type; 經過強轉來獲取泛型的具體實現類的class對象。

2,若是使用了<? extends Number>通配符來使用泛型信息,則反射獲得的Type的實現類爲sun.reflect.generics.reflectiveObjects.WildcardTypeImpl,該類的直接父接口爲java.lang.reflect.WildcardType。該接口的方法有:Type[] getUpperBounds();和Type[] getLowerBounds();來獲取上下邊界。代碼實例:

public class MyBox extends Box<Number> {
    public List<String> list;
    public List<Long> get(List<? extends Integer> list){
        return new ArrayList<Long>();
    }
    public static void main(String[] args) throws Exception {
        Type[] genericParameterTypes = MyBox.class.getMethod("get", List.class).getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {
            if (genericParameterType instanceof ParameterizedType) {
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for (Type type : actualTypeArguments) {
                   if (type instanceof WildcardType) {
                       Type[] upperBounds = ((WildcardType) type).getUpperBounds();
                       for (Type upperBoundType : upperBounds) {
                        System.out.println(upperBoundType.getTypeName());
                    }
                   }
                }
            }
        }

    }
}
相關文章
相關標籤/搜索