做网络请求的时候肯定要封装回调,我这里就传了泛型,但是出了个问题是Gson没办法直接解析泛型,如果直接解析的话,不会得到一个javabean而是得到一个LinkedTreeMap。
然后我去网上找了很就,都没有直接能把LinkedTreeMap转成javabean的方法,但是我们不可能给每个请求的结果都单独去写一个解析,这时候我们就不得不去正面一个问题:如何使用Gson来解析泛型
而我这篇文章都会去围绕这个问题去讲,表面上讲gson,实际上是讲java的type
因为是分2天写的,前面有点乱,可以直接看总结
一.使用Gson来解析JSON
我们先看看平时如何使用Gson来解析json,就假设有个Test类吧。
Test test = gson.fromJson(json, Test.class);
一般在明确对象类型之后我们确实可以这样做,但是如果是泛型呢
T test = gson.fromJson(json, T.class);
肯定不能这样玩,这不符合泛型的思想,而且也没有T.class,所以需要换种方法来做
这个方法的第二个参数是传一个Type,我们可以来看看什么是Type
都知道能理解成是一个类型的接口,但是从include这句话可以更加清楚的知道他是一个什么意思。
二.获取Type
一般你可以很容易的找到这样做
Type type = new TypeToken<Test>() {}.getType();
JsonBean jsonBean = gson.fromJson(json, type);
然后你觉得如果解析泛型的话可以这样
Type type = new TypeToken<T>() {}.getType();
JsonBean jsonBean = gson.fromJson(json, type);
这个我没试过,但是我知道如果传的不是泛型,而是一个包含泛型的类,最后解析出来的还是LinkedTreeMap,比如这样
Type type = new TypeToken<Result<T>>() {}.getType();
JsonBean jsonBean = gson.fromJson(json, type);
因为一般网络请求的数据结构我们都会这样做Result<T>,而这样是没法正常解析出我们想要的对象的。
这个时候网上就有一篇文章写得特别好
https://www.jianshu.com/p/d62c2be60617
可以看到需要重写一个类继承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;}
}
然后按照他的写法
Type type = new ParameterizedTypeImpl(Result.class, new Class[]{clazz});
那么假如我写一个解析类,我要怎么去获取到type,我就假如我自己写的okhttp的回调
public abstract class HttpCallback<T> implements Callback {
........
}
我要怎么在内部根据泛型来获取到Type对象,然后再根据type解析出javabean,还是假如我们的泛型从外面传进来的是Test
其实网上有很多代码,我可以一步一步调试分析出对象是什么
Type type = getClass().getGenericSuperclass();
这个方法也可以获取到Type对象,getClass()是Object类的方法,得到当前类的Class对象,这个很容易懂吧,getGenericSuperclass()是Class类的方法,得到这个Class类对象的Type对象,我们可以调试发现这里的getClass().getGenericSuperclass()为HttpCallback<Test>
拿到这个对象之后发现发现别人都是这样写的
Type[] types = ((ParameterizedType)type).getActualTypeArguments();
这里用到了一个ParameterizedType去强转对象,ParameterizedType中其实一共有是三个方法
(1)getActualTypeArguments()
(2)getOwnerType()
(3)getRawType()
我们来分别看看他们得到的结果
可以看到getActualTypeArguments得到的一个数组当前只有一个元素types[0] = Test。
getOwnerType()得到null。
getRawType()得到HttpCallback
从这里我们就可以知道,我们需要使用的是getActualTypeArguments()方法来获取我们要的Type
现在再来想想,我们需要的Type是用泛型转成的Result<Test>
getActualTypeArguments获得Test,getRawType()获得外层的类型,那把他们拼起来
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;}
}
就能获取到raw<args[0]>的type,那我们要获取Result<Test>,raw就是Result.class,<Test>可以根据((ParameterizedType)type).getActualTypeArguments()来获取。那我们可以这样写
Class<T> tClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; // 根据当前类获取泛型的Type
Type ty = new ParameterizedTypeImpl(BaseResponse.class, new Class[]{tClass}); // 传泛型的Type和我们想要的外层类的Type来组装我们想要的类型
JsonBean jsonBean = gson.fromJson(json, ty);
这样就能正常的解析到我们想要的javabean,注意,这个过程中最主要的是一个组装的思想,用到ParameterizedType 。那有的人问,为什么不可以直接用ParameterizedType 来操作,我们可以看看ParameterizedType 。
可以看到它是一个接口,继承Type
为什么要说这个,因为我感觉这个和他Type的一个思想有些关系,要理解别人是怎么去定义这个东西是什么,我们应该怎么去把这个东西说出来。先看getActualTypeArguments的注释
Returns an array of {@code Type} objects representing the actual type arguments to this type.
这里有个actual表示真实的,我们获取到的type[]数组内部的元素就是actual,那有必要看看什么是actual,现在我只定义了一个泛型,type[]数组也就只有一个actual,这个actual会得到传进来的泛型的类型,那假如我没有定义泛型和定义两个泛型呢?
(1)没有定义泛型的情况
如果没有泛型的话,getClass().getGenericSuperclass()会得到HttpCallBack,((ParameterizedType)type).getActualTypeArguments()会报类型转换错误: java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
(2)两个泛型的情况
getClass().getGenericSuperclass()会得到HttpCallBack<String,String>,我外面传的是两个String。
((ParameterizedType)type).getActualTypeArguments()得到的数组有两个元素
(3)当类不是抽象类的时候
上面的方法都是我在定义的类是抽象类的时候才能起作用,比如我两个参数的类
那么不是抽象类的时候,getClass().getGenericSuperclass()不管你传多少个参数都会得到Object
((ParameterizedType)type).getActualTypeArguments()自然也会报参数错误
那么如果是List数组的话呢?我们传入的泛型如果是List<T>的话
Type type = getClass().getGenericSuperclass();
得到HttpCallback<List<Test>>
Type[] types = ((ParameterizedType)type).getActualTypeArguments();
types[0]得到的是List<Test>
这样就能把types[0]和我们想要的对象Result传到自定义的ParameterizedType中组装。
可以看出我们就不需要像之前一样获取Class<T>对象,因为这个对象没法装List,直接获取到Type[] types就行。
三.总结
1.gson解析泛型的方法
Type type = getClass().getGenericSuperclass();
Type[] types = ((ParameterizedType)type).getActualTypeArguments();
Type ty = new ParameterizedTypeImpl(Result.class, new Type[]{types[0]});
Result<T> data = gson.fromJson(josndata, ty);
2.解析时的限制
(1)type不能转成ParameterizedType的情况
上面说了没定义泛型的时候type转成ParameterizedType会报类型转换错误,所以要加个判断
Type type = getClass().getGenericSuperclass();
if(type instanceof ParameterizedType){
Type[] types = ((ParameterizedType)type).getActualTypeArguments();
Type ty = new ParameterizedTypeImpl(Result.class, new Type[]{types[0]});
Result<T> data = gson.fromJson(josndata, ty);
}
(2)当前类不为抽象类的话type会得到Object,虽然我们用上面的判断能防止报错,但是这样也无法正常解析出数据,所以我们这个解析泛型的类必须是抽象类。至于为什么只有抽象类才能正常解析出而一般的类的type都拿到是Object,这点我就不是很清楚了。
3.Type[]数组
Type[]数组types中的数量是泛型的数量,比如传的泛型是<String,Test>,那这个数组就有两个值
types[0] = String
types[1] = Test
4.传的泛型是一个数组
public abstract class HttpCallBack<T> {
public HttpCallBack(){
init();
}
private void init(){
Type type = getClass().getGenericSuperclass();
Type[] types = ((ParameterizedType)type).getActualTypeArguments();
}
}
new HttpCallBack<List<String>>(){
};
直接贴结果
5.自定义一个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;}
}
Type ty = new ParameterizedTypeImpl(Result.class, new Type[]{types[0]});
拿到type之后调gson就行了