由于后面講到的反序列化器在反序列化List的時候需要確定泛型的type趁耗,所以這邊先講一下針對類型擦除的泛型串稀,我們要如何獲取其type
JAVA反射機制提供了運行時動態(tài)編程的可能
- 在運行階段能夠隨意的獲取:
- 任意一個類的所有屬性拆吆、函數(shù)與注解等信息;
- 任意一個對象,都能夠調(diào)用它的任意屬性與函數(shù)蜓席。
- 這種動態(tài)獲取信息以及動態(tài)調(diào)用對象方法的功能稱為java語言的反射機制。
眾所周知Java有個Object 類课锌,是所有Java 類的繼承根源厨内,其內(nèi)聲明了數(shù)個應(yīng)該在所有Java 類中被改寫的方法:hashCode()、equals()渺贤、clone()雏胃、
toString()、getClass()等志鞍。其中g(shù)etClass()返回一個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中的所有內(nèi)容,getXX: 獲得當(dāng)前類以及父類的內(nèi)容,但是不包括非public
類中的屬性對應(yīng)反射中的Field玻孟,而函數(shù)則為Method唆缴。但是獲取構(gòu)造方法,則需要通過Constructor:
Constructor<?>[] constructors = beanClass.getConstructors();
操作屬性黍翎、調(diào)用函數(shù)的方法則需要編寫:
Method method = XX;
//在obj對象上調(diào)用函數(shù)
method.invoke(obj,...);
Field field = XX;
//獲得obj中的屬性
field.get(obj);
//設(shè)置obj中的屬性
field.set(obj,value);
//調(diào)用構(gòu)造函數(shù)
Constructor<?>[] constructor = XX;
constructor.newInstance(...);
對于非public的Field/Method進行操作,需要先進行:setAccessible(true)
查看Bean的定義面徽,其中包括了List list成員,如何需要通過這個Field獲得對應(yīng)的泛型類型(即String)?
Java 泛型在運行的時候是會進行類型擦除匣掸,泛型信息只存在于代碼編譯階段趟紊。即對于List和List在運行階段都是List.class,泛型信息被擦除了。
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
assert(l1.getClass() == l2.getClass()); //結(jié)果為真
所以這時候沒法通過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");
// 屬性對應(yīng)的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() {
//獲取當(dāng)前對象的直接超類的 Type
Type superClass = getClass().getGenericSuperclass();
Type oriType = ((ParameterizedType) superClass).getActualTypeArguments()[0];
}
}
new TypeReference<Object>(){}.getType()
在運行期可以通過反射機制獲取到Class對應(yīng)的泛型類型照卦。這里為什么沒有“類型擦除”?
觀察上面的類,如果將構(gòu)造函數(shù)的作用域改為public,那么會得到一個
java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
如果將構(gòu)造函數(shù)改為public历极,則需要將類聲明為abstract才不會出現(xiàn)問題窄瘟。
這下子應(yīng)該明顯了衷佃,當(dāng)new這個類的時候趟卸,實際上是創(chuàng)建了一個匿名內(nèi)部類。TypeReference的子類氏义,泛型參數(shù)限定為Object锄列。
引入泛型擦除的原因是避免因為引入泛型而導(dǎo)致運行時創(chuàng)建不必要的類。Java 的泛型擦除是有范圍的惯悠,除了結(jié)構(gòu)化信息外的所有東西會擦除邻邮,與類及其字段和方法的類型參數(shù)相關(guān)的元數(shù)據(jù)都會被保留下來,可以通過反射獲取到克婶。
這里則是通過定義類(匿名內(nèi)部類)的方式筒严,在類信息中保留泛型信息,從而在運行時獲得這些泛型信息.