當反射遇上泛型該如何處理

由于后面講到的反序列化器在反序列化List的時候需要確定泛型的type,所以這邊先講一下針對類型擦除的泛型,我們要如何獲取其type

JAVA反射機制提供了運行時動態(tài)編程的可能

  • 在運行階段能夠隨意的獲?。?/li>
  • 任意一個類的所有屬性、函數(shù)與注解等信息;
  • 任意一個對象,都能夠調用它的任意屬性與函數(shù)。
  • 這種動態(tài)獲取信息以及動態(tài)調用對象方法的功能稱為java語言的反射機制。
    眾所周知Java有個Object 類,是所有Java 類的繼承根源,其內聲明了數(shù)個應該在所有Java 類中被改寫的方法:hashCode()、equals()、clone()、
    toString()、getClass()等。其中getClass()返回一個Class 對象。
    Class是反射的起源。
public class Bean {
    private int i;
    public List<String> list;

    public Bean() {

    }

    public void setI(int i) {
        this.i = i;
    }

    public int getI() {
        return i;
    }
}

有類如上。執(zhí)行下面代碼:

Class<Bean> beanClass = Bean.class;
        //獲得類以及父類中所有聲明為public的屬性
        System.out.println("所有public屬性:");
        for (Field field : beanClass.getFields()) {
            System.out.println(field.getName());
        }
        //獲得類(不包括父類)中所有的屬性
        System.out.println("所有屬性:");
        for (Field field : beanClass.getDeclaredFields()) {
            System.out.println(field.getName());
        }
        //獲得類以及父類中所有聲明為public的函數(shù)
        System.out.println("所有public函數(shù):");
        for (Method method : beanClass.getMethods()) {
            String methodName = method.getName();
            System.out.println(methodName);
        }

         //獲得類(不包括父類)中所有的函數(shù)
        System.out.println("所有函數(shù):");
        for (Method method : beanClass.getDeclaredMethods()) {
            String methodName = method.getName();
            System.out.println(methodName);
        }

很顯然,getDeclaredXX :會獲得Class中的所有內容,getXX: 獲得當前類以及父類的內容,但是不包括非public
類中的屬性對應反射中的Field,而函數(shù)則為Method。但是獲取構造方法,則需要通過Constructor:

Constructor<?>[] constructors = beanClass.getConstructors();

操作屬性、調用函數(shù)的方法則需要編寫:

Method method = XX;
//在obj對象上調用函數(shù)
method.invoke(obj,...);

 Field field = XX;
//獲得obj中的屬性
field.get(obj);
//設置obj中的屬性
field.set(obj,value);

//調用構造函數(shù)
Constructor<?>[] constructor = XX;
constructor.newInstance(...);

對于非public的Field/Method進行操作,需要先進行:setAccessible(true)
查看Bean的定義,其中包括了List list成員,如何需要通過這個Field獲得對應的泛型類型(即String)?
Java 泛型在運行的時候是會進行類型擦除,泛型信息只存在于代碼編譯階段。即對于List和List在運行階段都是List.class,泛型信息被擦除了。

List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
assert(l1.getClass() == l2.getClass()); //結果為真

所以這時候沒法通過Class<?> type = Field.getType獲得需要的Class。好在Java為我們提供了 Type 接口,使用它我們可以得到這些信息。
Type是一個接口,它的實現(xiàn)類有Class,子接口有 ParameterizedType, WildcardType等。

ParameterizedType (參數(shù)化類型)

Type[] getActualTypeArguments(): 返回類型的參數(shù)的實際類型數(shù)組
如Map<String,Integer>的類型(Type)是屬于參數(shù)化類型(ParameterizedType),
則可以獲得:

Type key = getActualTypeArguments()[0];
Type value = getActualTypeArguments()[1];

其中Key即為String,Value則是Integer。

WildcardType (通配符的類型)

JAVA泛型通配符的使用規(guī)則:"PECS"

在泛型中可以通過通配符來聲明一個泛型類型,即使用?。同時可以指明?的上下邊界。帶上邊界的通配符:
List<? extends Number> list;帶下邊界的通配符:List<? super Integer> list。

getUpperBounds:獲得上邊界

對于List<? extends Number> list上邊界為Number,而List<? super Integer> list上邊界則為:Object。

如果希望獲得某個類中List集合的泛型類型,則可以:

//指定獲得Bean中l(wèi)ist屬性
Field list = Bean.class.getField("list");
// 屬性對應的Class如果是List或其子類
if (List.class.isAssignableFrom(list.getType())) {
    //獲得 Type
    Type genericType = list.getGenericType();
    //ParameterizedType
    if (genericType instanceof ParameterizedType) {
        //獲得泛型類型
        Type type = ((ParameterizedType) genericType).getActualTypeArguments()[0];
        //WildcardType  如果使用了通配符
        if (type instanceof WildcardType) {
            WildcardType wildcardType = (WildcardType) type;
            Type[] upperBounds = wildcardType.getUpperBounds();
            if (upperBounds.length == 1) {
                Type actualTypeArgument = upperBounds[0];
                System.out.println("獲得泛型上邊界類型:" + actualTypeArgument);
            }
        } else {
            System.out.println("獲得泛型類型:" + type);
        }
    } 
}

如果遇見一個泛型類

public class TypeReference<T> {
    protected TypeReference() {
        //獲取當前對象的直接超類的 Type
        Type superClass = getClass().getGenericSuperclass();
        Type oriType = ((ParameterizedType) superClass).getActualTypeArguments()[0];
    }
}
new TypeReference<Object>(){}.getType()

在運行期可以通過反射機制獲取到Class對應的泛型類型。這里為什么沒有“類型擦除”?
觀察上面的類,如果將構造函數(shù)的作用域改為public,那么會得到一個

java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType

如果將構造函數(shù)改為public,則需要將類聲明為abstract才不會出現(xiàn)問題。
這下子應該明顯了,當new這個類的時候,實際上是創(chuàng)建了一個匿名內部類。TypeReference的子類,泛型參數(shù)限定為Object。
引入泛型擦除的原因是避免因為引入泛型而導致運行時創(chuàng)建不必要的類。Java 的泛型擦除是有范圍的,除了結構化信息外的所有東西會擦除,與類及其字段和方法的類型參數(shù)相關的元數(shù)據(jù)都會被保留下來,可以通過反射獲取到。
這里則是通過定義類(匿名內部類)的方式,在類信息中保留泛型信息,從而在運行時獲得這些泛型信息.

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容