作者: @怪盜kidou
如需轉載需在明顯位置保留作者信息及原文鏈接
如果博客中有不恰當之處歡迎留言交流
http://www.jianshu.com/p/d62c2be60617
在你真的會用Gson嗎?Gson使用指南(一) 的第三節我介紹了在Gson中如何使用泛型來簡化我們的類設計,但隨之而來引入了一個新的問題:封裝。不知道各位有沒有想過這樣一個問題:每次都要用 new TypeToken<XXX>(){};
好麻煩,有沒有更好的辦法?
有更好的辦法麼?當然有!相信也有不少人自己作了嘗試,只是有人歡喜有人愁了,不過沒關係,今天我們就來解決這個問題。
約定
1、本文涉及到的json格式
// data 爲 object 的情況
{"code":"0","message":"success","data":{}}
// data 爲 array 的情況
{"code":"0","message":"success","data":[]}
2、假定第一種的對應的Java類型爲 Result<XXX>
,第二種爲 Result<List<XXX>>
一、爲何封裝,如何封裝
1、爲何封裝:
- 寫
new TypeToken<XXX>(){}
麻煩,IDE格式化後還不好看 - 不同的地方每進行一次
new TypeToken<XXX>(){}
操作都會生成一個新的類 - 對於任意類XXX都只有兩種情況
new TypeToken<Result<XXX>>(){}
和new TypeToken<Result<List<XXX>>>(){}
- 方便統一管理
2、如何封裝
從上面的我們可以知道,最簡單的方法就是提供兩個方法分別對應data
爲Array和Object的情況並接收一個參數,即告知XXX的類型,自動將完成new TypeToken<XXX>(){}
與new TypeToken<Result<List<XXX>>>(){}
的過程。
方法原型:
// 處理 data 爲 object 的情況
public static <T> Result<T> fromJsonObject(Reader reader, Class<T> clazz) {}
// 處理 data 爲 array 的情況
public static <T> Result<List<T>> fromJsonArray(Reader reader, Class<T> clazz){}
二、爲何失敗?
對於那些嘗試着封裝過的人可能都這麼寫過:
public static <T> Result<List<T>> fromJsonArray(Reader reader) {
Type type = new TypeToken<Result<List<T>>>(){}.getType();
return GSON.fromJson(reader, type);
}
當然上面的寫法肯定是沒有辦法完成的,雖然代碼不會報錯,但運行結果肯定是不對的,因爲這裏的T
其實是一個 TypeVariable
,他在運行時並不會變成我們想要的XXX,所以通過TypeToken
得到的 泛型信息只是 "Result<List<T>>"
。
三、如何解決?
既然TypeToken
的作用是用於獲取泛型的類,返回的類型爲Type
,真正的泛型信息就是放在這個Type
裏面,既然用TypeToken
生成會有問題,那我們自己生成Type就行了嘛。
Type
是Java中所有類型的父接口,在1.8以前是一個空接口,自1.8起多了個getTypeName()
方法,下面有ParameterizedType、 GenericArrayType、 WildcardType、 TypeVariable
幾個接口,以及Class類。這幾個接口在本次封裝過程中只會用到 ParameterizedType
,所以簡單說一下:
ParameterizedType
簡單說來就是形如 ** 類型<> ** 的類型,如:Map<String,User>
。下面就以 Map<String,User>
爲例講一下里面各個方法的作用。
public interface ParameterizedType extends Type {
// 返回Map<String,User>裏的String和User,所以這裏返回[String.class,User.clas]
Type[] getActualTypeArguments();
// Map<String,User>裏的Map,所以返回值是Map.class
Type getRawType();
// 用於這個泛型上中包含了內部類的情況,一般返回null
Type getOwnerType();
}
所以,知道了這裏需要的泛型是怎麼回事,一切都好說了,下面我們來完成之前留下的空方法。
1、實現一個簡易的 ParameterizedType
public class ParameterizedTypeImpl implements ParameterizedType {
private final Class raw;
private final Type[] args;
public ParameterizedTypeImpl(Class raw, Type[] args) {
this.raw = raw;
this.args = args != null ? args : new Type[0];
}
@Override
public Type[] getActualTypeArguments() {
return args;
}
@Override
public Type getRawType() {
return raw;
}
@Override
public Type getOwnerType() {return null;}
}
2、生成Gson需要的泛型
2.1解析data是object的情況
public static <T> Result<T> fromJsonObject(Reader reader, Class<T> clazz) {
Type type = new ParameterizedTypeImpl(Result.class, new Class[]{clazz});
return GSON.fromJson(reader, type);
}
2.2解析data是array的情況
是Array的情況要比是Object的情況多那麼一步。
public static <T> Result<List<T>> fromJsonArray(Reader reader, Class<T> clazz) {
// 生成List<T> 中的 List<T>
Type listType = new ParameterizedTypeImpl(List.class, new Class[]{clazz});
// 根據List<T>生成完整的Result<List<T>>
Type type = new ParameterizedTypeImpl(Result.class, new Type[]{listType});
return GSON.fromJson(reader, type);
}
本次代碼較少,不提供源碼
雖然這篇博客是以Gson爲例,但從上面的內容可以看出實際上和Gson關係不大,主要的內容還是Java的泛型基礎,所以這種封裝的方法同樣適用於其它的框架。
最後借這次機會給安利一個簡易的泛型生成庫 TypeBuilder ,其最初實現的目的就是讓大家快速的生成泛型信息,同時也會作一些參數檢查,保證正確性。
用上面的代碼給大家舉個例子
public static <T> Result<List<T>> fromJsonArray(Reader reader, Class<T> clazz) {
Type type = TypeBuilder
.newInstance(Result.class)
.beginSubType(List.class)
.addTypeParam(clazz)
.endSubType()
.build();
return GSON.fromJson(reader, type);
}
public static <T> Result<T> fromJsonObject(Reader reader, Class<T> clazz) {
Type type = TypeBuilder
.newInstance(Result.class)
.addTypeParam(clazz)
.build();
return GSON.fromJson(reader, type);
}