java反射與泛型

反射中與泛型相關的接口和類

1 Type
java中所有類型的公共實現接口,實現該接口的有原始類型,參數化類型,數組類型,類型變量和基本類型。

2 GenericDeclaration
聲明類型變量的所有實體的公共接口

`TypeVariable<?>[] getTypeParameters();`返回泛型聲明中的類型變量   如Map<K,V>中返回的是鍵值K,V

3 ParameterizedType 參數化類型

    Type[] getActualTypeArguments();
    Type getRawType();  原始類型 ,如Class<T>的實例類Class<String>的RawType是Class
    Type getOwnerType(); 泛型類是內部類

泛型與反射

Java中的泛型是一種僞泛型,會在編譯期間被擦出,所以無法在運行時獲得有關泛型的信息。
但是在某些情況下,可以在運行期獲得泛型的信息。

下面是兩個典型的使用泛型的場景:
1 聲明一個需要被參數化(parameterizable)的類或者接口
2 使用一個參數化的類
當你聲明一個類或者接口的時候你可以指明這個類或接口可以被參數化, java.util.List 接口就是典型的例子。你可以運用泛型機制創建一個標明存儲的是String類型List,這樣比你創建一個Object的List要更好。
你不能在運行期獲知一個被參數化的類型的具體參數類型是什麼,但是你可以在用到這個被參數化類型的方法以及變量中找到他們,換句話說就是獲知他們具體的參數化類型。

  • 泛型方法返回類型

獲得了java.lang.reflect.Method對象,那麼就可以獲取到這個方法的泛型返回類型信息。
如果方法是在一個被參數化類型之中(如 T fun())那麼你無法獲取他的具體類型,但是如果方法返回一個泛型類(譯者注:如 List fun())那麼你就可以獲得這個泛型類的具體參數化類型。)

class MyClass {
        private List<String> stringList = new ArrayList<String>();
        public List<String> getStringList() {
            return this.stringList;
        }
}

我們可以獲取 getStringList()方法的泛型返回類型,換句話說,我們可以檢測到 getStringList()方法返回的是 List 而不僅僅只是一個 List。

Method method = MyClass.class.getMethod("getStringList",null);
        Type returnType = method.getGenericReturnType();
        if(returnType instanceof ParameterizedType) {
            ParameterizedType type = (ParameterizedType)returnType;
            Type[] typeArguments = type.getActualTypeArguments();
            for(Type typeArgument : typeArguments) {
                Class typeArgClass = (Class) typeArgument;
                System.out.println("typeArgClass = " + typeArgClass);
            }
    }

輸出:typeArgClass = class java.lang.String
Class類實現了Type接口,這裏通過反射獲得了參數類型的信息。

  • 泛型方法參數類型

可以通過反射來獲取方法參數的泛型類型。

method = MyClass.class.getMethod("setStringList", List.class);
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        for(Type genericParameterType : genericParameterTypes){
            if(genericParameterType instanceof ParameterizedType){
                ParameterizedType aType = (ParameterizedType) genericParameterType;
                Type[] parameterArgTypes = aType.getActualTypeArguments();
                for(Type parameterArgType : parameterArgTypes){
                    Class parameterArgClass = (Class) parameterArgType;
                    System.out.println("parameterArgClass = " + parameterArgClass);
                }
            }
        }

輸出:parameterArgClass = class java.lang.String

  • 泛型變量類型

通過反射可以訪問公有(public)變量的泛型類型。無論是一個類的靜態成員變量還是實例成員變量,都可以獲取到。

Field field = MyClass.class.getField("stringList");
        Type genericFieldType = field.getGenericType();
        if(genericFieldType instanceof ParameterizedType){
            ParameterizedType aType = (ParameterizedType) genericFieldType;
            Type[] fieldArgTypes = aType.getActualTypeArguments();
            for(Type fieldArgType : fieldArgTypes){
                Class fieldArgClass = (Class) fieldArgType;
                System.out.println("fieldArgClass = " + fieldArgClass);
            }
        }

輸出:fieldArgClass = class java.lang.String

補充:在集合中使用參數化類型,這樣禁止我們在編譯期間插入與類型不符合的參數,但是通過反射,由於反射是在運行期發生作用,且java中的泛型在編譯期間就會被擦除了,因此可以跳過編譯期間的檢查,插入任意類型的數據。

public class GenericEssence {
    public static void main(String[] args) {
        List list1 = new ArrayList(); // 沒有泛型 
        List<String> list2 = new ArrayList<String>(); // 有泛型


        /*
         * 1.首先觀察正常添加元素方式,在編譯器檢查泛型,
         * 這個時候如果list2添加int類型會報錯
         */
        list2.add("hello");
//      list2.add(20); // 報錯!list2有泛型限制,只能添加String,添加int報錯
        System.out.println("list2的長度是:" + list2.size()); // 此時list2長度爲1


        /*
         * 2.然後通過反射添加元素方式,在運行期動態加載類,首先得到list1和list2
         * 的類類型相同,然後再通過方法反射繞過編譯器來調用add方法,看能否插入int
         * 型的元素
         */
        Class c1 = list1.getClass();
        Class c2 = list2.getClass();
        System.out.println(c1 == c2); // 結果:true,說明類類型完全相同

        // 驗證:我們可以通過方法的反射來給list2添加元素,這樣可以繞過編譯檢查
        try {
            Method m = c2.getMethod("add", Object.class); // 通過方法反射得到add方法
            m.invoke(list2, 20); // 給list2添加一個int型的,上面顯示在編譯器是會報錯的
            System.out.println("list2的長度是:" + list2.size()); // 結果:2,說明list2長度增加了,並沒有泛型檢查
        } catch (Exception e) {
            e.printStackTrace();
        }
        /*
         * 綜上可以看出,在編譯器的時候,泛型會限制集合內元素類型保持一致,但是編譯器結束進入
         * 運行期以後,泛型就不再起作用了,即使是不同類型的元素也可以插入集合。
         */
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章