1. 程式人生 > >Android:Gson通過藉助TypeToken獲取泛型引數的型別的方法

Android:Gson通過藉助TypeToken獲取泛型引數的型別的方法

最近在使用Google的Gson包進行Json和Java物件之間的轉化,對於包含泛型的類的序列化和反序列化Gson也提供了很好的支援,感覺有點意思,就花時間研究了一下。 由於Java泛型的實現機制,使用了泛型的程式碼在執行期間相關的泛型引數的型別會被擦除,我們無法在執行期間獲知泛型引數的具體型別(所有的泛型型別在執行時都是Object型別)。 但是有的時候,我們確實需要獲知泛型引數的型別,比如將使用了泛型的Java程式碼序列化或者反序列化的時候,這個時候問題就變得比較棘手。

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

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

對於上面的類Foo,由於在執行期間無法得知T的具體型別,對這個類的物件進行序列化和反序列化都不能正常進行。Gson通過藉助TypeToken類來解決這個問題。

  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()方法獲取到我們使用的泛型類的泛型引數型別。

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

要獲取泛型引數的型別,一般的做法是在使用了泛型的類的建構函式中顯示地傳入泛型類的Class型別作為這個泛型類的私有屬性,它儲存了泛型類的型別資訊。

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的型別,可以通過另一種方式實現:

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欄位指明這個常量池的地址。

JDK裡面提供了方法去讀取這些泛型資訊的方法,再借助反射,就可以獲得泛型引數的具體型別。同樣是對於第一段程式碼中的foo物件,通過下面的程式碼可以得到foo中的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類的程式碼。