java取得泛型的參數類型

Gson經過藉助TypeToken獲取泛型參數的類型的方法html

最近在使用Google的Gson包進行Json和Java對象之間的轉化,對於包含泛型的類的序列化和反序列化Gson也提供了很好的支持,感受有點意思,就花時間研究了一下。java

因爲Java泛型的實現機制,使用了泛型的代碼在運行期間相關的泛型參數的類型會被擦除,咱們沒法在運行期間獲知泛型參數的具體類型(全部的泛型類型在運行時都是Object類型)。json

可是有的時候,咱們確實須要獲知泛型參數的類型,好比將使用了泛型的Java代碼序列化或者反序列化的時候,這個時候問題就變得比較棘手。數組


class Foo<T> {
 T value;
}
Gson gson = new Gson();
Foo<Bar> foo = new Foo<Bar>();
gson.toJson(foo); // May not serialize foo.value correctlyide

gson.fromJson(json, foo.getClass()); // Fails to deserialize foo.value as Bar函數


對於上面的類Foo<T>,因爲在運行期間沒法得知T的具體類型,對這個類的對象進行序列化和反序列化都不能正常進行。Gson經過藉助TypeToken類來解決這個問題。this


TestGeneric<String> t = new TestGeneric<String>();
 t.setValue("Alo");
 Type type = new TypeToken<TestGeneric<String>>(){}.getType();

 String gStr = GsonUtils.gson.toJson(t,type);
 System.out.println(gStr);
 TestGeneric t1 = GsonUtils.gson.fromJson(gStr, type);
 System.out.println(t1.getValue());


TypeToken的使用很是簡單,如上面的代碼,只要將須要獲取類型的泛型類做爲TypeToken的泛型參數構造一個匿名的子類,就能夠經過getType()方法獲取到咱們使用的泛型類的泛型參數類型。spa

下面來簡單分析一下原理。code

要獲取泛型參數的類型,通常的作法是在使用了泛型的類的構造函數中顯示地傳入泛型類的Class類型做爲這個泛型類的私有屬性,它保存了泛型類的類型信息。htm


public class Foo<T>{

public Class<T> kind;

public Foo(Class<T> clazz){
 this.kind = clazz;
}

public T[] getInstance(){
 return (T[])Array.newInstance(kind, 5);
}

public static void main(String[] args){
 Foo<String> foo = new Foo(String.class);
 String[] strArray = foo.getInstance();
}

}


這種方法雖然能解決問題,可是每次都要傳入一個Class類參數,顯得比較麻煩。Gson庫裏面對於這個問題採用了了另外一種解決辦法。

一樣是爲了獲取Class的類型,能夠經過另外一種方式實現:

文章出自:http://www.ishang123.com/jishubowen/java/2012-07-28/100.html


public abstract class Foo<T>{

Class<T> type;

public Foo(){
 this.type = (Class<T>) getClass();
}

       public static void main(String[] args) {

 Foo<String> foo = new Foo<String>(){};
 Class mySuperClass = foo.getClass();

}

}


聲明一個抽象的父類Foo,匿名子類將泛型類做爲Foo的泛型參數傳入構造一個實例,再調用getClass方法得到這個子類的Class類型。

這裏雖然經過另外一種方式得到了匿名子類的Class類型,可是並無直接將泛型參數T的Class類型傳進來,那又是如何得到泛型參數的類型的呢,這要依賴Java的Class字節碼中存儲的泛型參數信息。Java的泛型機制雖然在運行期間泛型類和非泛型類都相同,可是在編譯java源代碼成class文件中仍是保存了泛型相關的信息,這些信息被保存在class字節碼常量池中,使用了泛型的代碼處會生成一個signature簽名字段,經過簽名signature字段指明這個常量池的地址。

關於class文件中存儲泛型參數類型的具體的詳細的知識能夠參考這裏:http://stackoverflow.com/questions/937933/where-are-generic-types-stored-in-java-class-files

JDK裏面提供了方法去讀取這些泛型信息的方法,再借助反射,就能夠得到泛型參數的具體類型。一樣是對於第一段代碼中的foo對象,經過下面的代碼能夠獲得foo<T>中的T的類型:


Type mySuperClass = foo.getClass().getGenericSuperclass();
 Type type = ((ParameterizedType)mySuperClass).getActualTypeArguments()[0];
               System.out.println(type);



運行結果是class java.lang.String。

分析一下這段代碼,Class類的getGenericSuperClass()方法的註釋是:

Returns the Type representing the direct superclass of the entity (class, interface, primitive type or void) represented by thisClass.

If the superclass is a parameterized type, the Type object returned must accurately reflect the actual type parameters used in the source code. The parameterized type representing the superclass is created if it had not been created before. See the declaration of ParameterizedType for the semantics of the creation process for parameterized types. If thisClass represents either theObject class, an interface, a primitive type, or void, then null is returned. If this object represents an array class then theClass object representing theObject class is returned

歸納來講就是對於帶有泛型的class,返回一個ParameterizedType對象,對於Object、接口和原始類型返回null,對於數組class則是返回Object.class。ParameterizedType是表示帶有泛型參數的類型的Java類型,JDK1.5引入了泛型以後,Java中全部的Class都實現了Type接口,ParameterizedType則是繼承了Type接口,全部包含泛型的Class類都會實現這個接口。

實際運用中還要考慮比較多的狀況,好比得到泛型參數的個數避免數組越界等,具體能夠參看Gson中的TypeToken類及ParameterizedTypeImpl類的代碼。

相關文章
相關標籤/搜索