Type簡介
Type是Java反射中框架中的一個核心接口,用於獲取類型信息,它的定義很簡單
package java.lang.reflect;
public interface Type {
default String getTypeName() {
return this.toString();
}
}
我們在日常開發中,幾乎用不到這個接口,但在底層的JDK源碼中,Type則是一個非常重要的接口
我們常見的Class類就實現了Type接口,Type接口的另一個重要實現類是ParameterizedType
Class類只保存了當前類的基本類型信息,而ParameterizedType則保存了泛型,外部類等額外類型信息
如果我們想學得更加深入,自己寫出像Gson那樣的類型解析庫,支持泛型嵌套等各種複雜數據類型,則必須學習這些知識
下面我們來逐個學習下Type在Java源碼中的一些基本用法
獲取Class的接口實現和類繼承信息
List<String> list = new LinkedList();
Class<? extends List> clazz = list.getClass();
//獲取接口類型信息
Type[] genericInterfaces = clazz.getGenericInterfaces();
for (Type genericInterface : genericInterfaces)
System.out.println("Generic Interface:" + genericInterface.getTypeName());
//打印Type的具體類型
for (Type genericInterface : genericInterfaces)
System.out.println("Type Class:" + genericInterface.getClass().getName());
//獲取父類類型信息
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println("Generic Superclass:" + genericSuperclass.getTypeName());
//可見,通過Type可以保存Class的所有接口實現和類繼承信息
//由於Class中的泛型信息會被參數,TypeName只保留了類聲明時的泛型信息
//另外可以發現,帶泛型的Type都是通過ParameterizedType類型保存的,無泛型的Type則是通過Class來保存的
Generic Interface: java.util.List<E>
Generic Interface: java.util.Deque<E>
Generic Interface: java.lang.Cloneable
Generic Interface: java.io.Serializable
Type Class: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
Type Class: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
Type Class: java.lang.Class
Type Class: java.lang.Class
Generic Superclass: java.util.AbstractSequentialList<E>
數組的Class與Type
String[][][][] array = new String[][][][]{};
Class<? extends String[][][][]> clazz = array.getClass();
//獲取數組的Class和Type
System.out.println("Class Name: " + clazz.getName());
System.out.println("Type Name: " + clazz.getTypeName());
//獲取數組基類類型
Class<?> componentType = clazz.getComponentType();
System.out.println("Component Type: " + componentType.getTypeName());
Class Name: [[[[Ljava.lang.String;
Type Name: java.lang.String[][][][]
Component Type: java.lang.String[][][]
獲取Field的字段類型
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
public class Hello {
public String name;
public List<String> list;
public Map<Integer, Long> map;
public Hello.Inner inner;
public static class Inner {
}
public static void main(String[] args) throws Exception {
Field[] fields = Hello.class.getDeclaredFields();
//獲取字段的基本類型
for (Field field : fields) {
Class<?> type = field.getType();
System.out.println("Basic Type: " + type.getName());
}
//獲取字段的泛型類型
for (Field field : fields) {
Type genericType = field.getGenericType();
System.out.println("Generic Type: " + genericType.getTypeName());
}
//獲取Type的實際類型
for (Field field : fields) {
Type genericType = field.getGenericType();
System.out.println("Type Class: " + genericType.getClass().getName());
}
}
}
Basic Type: java.lang.String
Basic Type: java.util.List
Basic Type: java.util.Map
Basic Type: com.easing.java.Hello$Inner
Generic Type: java.lang.String
Generic Type: java.util.List<java.lang.String>
Generic Type: java.util.Map<java.lang.Integer, java.lang.Long>
Generic Type: com.easing.java.Hello$Inner
Type Class: java.lang.Class
Type Class: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
Type Class: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
Type Class: java.lang.Class
ParameterizedType
上面提到了,相比Class只保存基本類型信息,ParameterizedType還會保存泛型參數等額外的類型信息
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
public class Hello {
public String name;
public List<String> list;
public Map<Integer, Long> map;
public Map.Entry<Integer, Long> entry;
public static void main(String[] args) throws Exception {
Field field = Hello.class.getDeclaredField("entry");
ParameterizedType type = (ParameterizedType) field.getGenericType();
//獲取字段的基本類型
Type rawType = type.getRawType();
System.out.println("Raw Type: " + rawType.getTypeName());
//獲取字段泛型參數的類型
Type[] argumentTypes = type.getActualTypeArguments();
for (Type argumentType : argumentTypes)
System.out.println("Argument Type: " + argumentType.getTypeName());
//如果字段類型是個內部類,則獲取字段所在外部類的類型
Type ownerType = type.getOwnerType();
System.out.println("Owner Type: " + ownerType.getTypeName());
}
}
Raw Type: java.util.Map$Entry
Argument Type: java.lang.Integer
Argument Type: java.lang.Long
Owner Type: java.util.Map
Gson泛型解析原理
用過Gson的都知道,Gson是可以解析帶複雜泛型參數的數據結構的,方式如下
T data = new Gson().fromJson(json, new TypeToken<T>(){}.getType());
List<List<String>> data = new Gson().fromJson(json, new TypeToken<List<List<String>>>(){}.getType());
我們已經知道,Class在運行期間是會擦除具體泛型信息的,因爲Java代碼最終會被編譯爲機器指令,對CPU來說,並不需要知道對象的具體類型,只要知道參與運算的對象地址就行了。如果所有的泛型信息全部存儲起來,是一個很大的開銷
因此,想要告訴Gson庫具體的泛型類型,傳遞Class對象肯定是不行的。通過上面的代碼我們已經可以看出來,Gson是通過傳遞了一個特殊的Type對象來完成這個功能的
雖然Class會擦除具體泛型信息,但是上面已經提到過,Class是會通過Type記錄自己實現了哪些接口,繼承了哪些父類的,可以通過getGenericSuperclass獲取
我們以List爲例來驗證下這個知識點
//ArrayList類聲明
package java.util;
public class ArrayList<E> extends AbstractList<E> implements List<E> {
}
List<List<String>> list = new ArrayList();
//Class不會記錄自身的泛型信息
System.out.println("Class Type: " + list.getClass().getTypeName());
//Class會記錄類聲明時父類的泛型信息
//由於ArrayList聲明時並未指定父類的具體泛型,所以獲取到的就是佔位符<E>
System.out.println("Superc Class Type: " + list.getClass().getGenericSuperclass().getTypeName());
Class Type: java.util.ArrayList
Superc Class Type: java.util.AbstractList<E>
可以看到,Class中確實保存了父類的泛型信息,不過這裏的是通用佔位符E,還不是具體的類型
聰明的朋友應該已經猜到,區別在於我們是直接new了一個對象,而Gson在new後面還加上了一對大括號
大括號的作用是重寫父類方法,所以new XXX() {}的本質其實於聲明瞭一個匿名類繼承XXX,然後用匿名類創建了一個對象
講到這裏,基礎紮實的朋友應該已經恍然大悟,完全可以自己推測後面的事情了
這裏再多說一局題外話,技術上想要走的遠,光靠上班工作是不夠的,業餘時間還要多研究底層原理,多設計東西
有的人看到這裏已經不用多說了,而平時不怎麼學習的,一下接收這麼多信息可能還沒反應過來,這就是平時積累上的差距
我們看到,Gson在new TypeToken時是帶上了泛型的,這其實就相當於
class XXX extends TypeToken<List<List<String>>>
匿名類在聲明時,其實就已經指定了具體的父類泛型參數,所以在運行時是可以獲取到的,我們仍然用List來驗證下
//new後面帶上大括號,表示重寫父類方法,創建了一個匿名子類
List<List<String>> list = new ArrayList<List<String>>(){};
//打印對象的實際類型
System.out.println("Class Type: " + list.getClass().getTypeName());
//打印對象父類信息
System.out.println("Super Class Type: " + list.getClass().getGenericSuperclass().getTypeName());
Class Type: com.easing.java.Hello$1
Super Class Type: java.util.ArrayList<java.util.List<java.lang.String>>
可以看到,通過大括號重寫創建的對象,確實是子類,而且可以正確獲取到實際的泛型信息
模擬Gson的TypeToken類存儲泛型信息
這裏我們手寫一個TypeMarker類,來模擬TypeToken的功能
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
//定義爲抽象類,從而強制開發者重寫繼承此類
public abstract class TypeMarker<T> {
//獲取泛型參數類型
protected Type genericParamType() {
//獲取父類類型
//由於是抽象類,其實現類必然是繼承當前類,所以父類類型即是TypeMarker<XXX>
Type superType = getClass().getGenericSuperclass();
//如果沒有指定泛型參數,則返回的Type實際類型爲Class
//未指定泛型參數時,默認將泛型視爲Object類
if (superType instanceof Class)
return Object.class;
//如果有泛型參數,則返回的Type實際類型爲ParameterizedType
//強轉並獲取泛型參數,即XXX的實際類型
ParameterizedType parameterizedType = (ParameterizedType) superType;
Type argumentType = parameterizedType.getActualTypeArguments()[0];
return argumentType;
}
}
TypeMarker<List<List<List<String >>>> typeMarker = new TypeMarker<List<List<List<String >>>>(){};
System.out.println("Param Type: " + typeMarker.genericParamType());
Param Type: java.util.List<java.util.List<java.util.List<java.lang.String>>>
OK!格式是不是和Gson的一模一樣,功能也完全達到
這裏我們只是爲了證明,泛型信息是可以保存並傳遞給Gson庫的
實際Gson庫中的TypeToken類比這個複雜,因爲它還要完成其它功能
類內部的字段可能還包含泛型參數,要完成任意泛型類的完整解析,必須進行遞歸解析,所以必須包含其它功能
TypeToken寫法優化
如果覺得TypeToken的寫法很長很難看的話,自己寫一個類繼承泛型參數,再獲取這個類的父類Type就行了
//通用的HttpResponse類
//用於接收Http請求返回的Body數據
public class ResponseBody<T> {
public Integer code;
public String message;
public String[] detail;
public T data;
}
//接收登錄請求返回的數據
public class LoginResponse extends ResponseBody<User> {
public static Type type() {
return LoginResponse.class.getGenericSuperclass();
}
}
//Gson寫法
Type type1 = new TypeToken<ResponseBody<User>>(){}.getType();
//優化後的寫法
Type type2 = LoginResponse.type();
//獲取到的Type類型一致
System.out.println(type1.getTypeName());
System.out.println(type2.getTypeName());
//Gson解析
//注意,這裏的Type是ResponseBody<User>,所以也要用這個類型來接收
ResponseBody<User> response = new Gson().fromJson(json, type2);