在你真的会用Gson吗?Gson使用指南(一)的第三节我介绍了在 Gson 中如何使用泛型来简化我们的类设计,但随之而来引入了一个新的问题:封装。不知道各位有没有想过这样一个问题:每次都要用 new TypeToken<XXX>() {};
好麻烦,有没有更好的办法?
有更好的办法么?当然有!相信也有不少人自己作了尝试,只是有人欢喜有人愁了,不过没关系,今天我们就来解决这个问题。
约定
本文涉及到的 JSON 格式,如下所示:
// data 为 object 的情况
{"code":"0","message":"success","data":{}}
// data 为 array 的情况
{"code":"0","message":"success","data":[]}
假定第一种对应的 Java 类型为
Result<XXX>
,第二种为Result<List<XXX>>
一、为何封装,如何封装
1.1 为何封装
- 写
new TypeToken<XXX>() {}
麻烦,IDE 格式化后还不好看 - 不同的地方每进行一次
new TypeToken<XXX>() {}
操作都会生成一个新的类 - 对于任意类
XXX
都只有两种情况new TypeToken<Result<XXX>>() {}
和new TypeToken<Result<List<XXX>>>() {}
- 方便统一管理
1.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 中所有类型的父接口,在 JDK1.8 以前是一个空接口,自 JDK1.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();
}
所以,知道了这里需要的泛型是怎么回事,一切都好说了,下面我们来完成之前留下的空方法。
3.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;}
}
3.2 生成 Gson
需要的泛型
3.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);
}
3.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);
}