【Java】java.lang.reflect.Type詳解

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);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章