java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to xxx
一:正文前面的 叨叨
最近在写一个请求封装的时候翻车了,只能怪自己太菜了,在开始之前先看一下整体的流程(不需要的地方已经简化了)。
在一个正常的请求中,有很多个实体类(JavaBean),每个接口返回的数据也是各有千秋,均不相同,但是所有的接口最外层,有一层他们公有的结构体,例如:{“code”:1000,“msg”:“成功”,“data”:""};
于是为了不写重复的代码,我们可以将最外层的解析不写在每个请求的各自实现中,而是写在封装好的方法里,这里我的写法是这样的:
1:首先定义自己的数据返回体,利用接口将数据返回到上层;
(1)定义所有接口返回的最外层 基础结
public class BaseEntity<T> {
public String msg;
public int code;
public T data;
}
(2)定义符合所有请求返回数据的接口:
public interface RequestListener<T> {
void onSuccess(T data);
}
2:其次完成 所有请求的封装:
/**
* 这是模拟后台请求
*
* @param listener 用于传递数据的回调
* @param <T> 需要转换data 的对象类型
*/
private <T> void request(RequestListener<T> listener) {
String serverStr = "这里是后台返回的内容";
Type type = new TypeToken<BaseEntity<T>>() {
}.getType();
BaseEntity<T> baseEntity = new Gson().fromJson(serverStr, type);
T data = baseEntity.data;
listener.onSuccess(data);
}
3:发起一个请求(在这里我就只模拟一下简单的数据结构,请求在本文中是非重点):
(1)定义学生 Student 的JavaBean
public class Student {
public String name;
public int age;
public double score;
}
(2):发起一个 获取学生的请求:
private void getStudent() {
request(new RequestListener<Student>() {
@Override
public void onSuccess(Student data) {
}
});
}
(3)发起一个 获取所有学生的请求
private void getStudents() {
request(new RequestListener<List<Student>>() {
@Override
public void onSuccess(List<Student> data) {
}
});
}
4:开始准备数据进行测试:
(1)获取所有学生 请求,准备后台返回的数据:
数据:
{
"code":1000,
"msg":"成功",
"data":[
{
"age":19,
"name":"张三",
"score":95.5
},
{
"age":25,
"name":"李四",
"score":66.6
},
{
"age":20,
"name":"王五",
"score":75
},
{
"age":22,
"name":"马六",
"score":100
}
]
}
代码请求:
看着貌似也没啥问题对吧?
(2)获取学生 请求,准备后台返回的数据:
数据:
{
"code":1000,
"msg":"成功",
"data":{
"age":19,
"name":"张三",
"score":95.5
}
}
代码请求:
直接崩溃。接下来进入今日正文
**
二:直接崩溃 从这里是本文重点
1 发现错误:
**
报错信息 java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast Student;
凭借多年老油条的经验,凭直觉感觉,这里翻车了,因为一时半会想不起来哪里出了问题,而且获取所有学生信息都以及成功拿到数据了啊,这里为啥会翻车呢?
好了,不买关子了,其实细心的同学发现,在上面获取 所有学生信息接口的时候,List中明明是我定义的 Student 为什么在拿到数据时时 LinkedTreeMap?
这样就能说明其实获取全部学生时已经出现问题了,只不过我们并没有取到里面的item;
所以到这里就应该明白,其实我们的封装出问题了。
2 错误分析:
回过头来检查 我们的封装方法,其实一眼望去没啥大问题,就那么几行代码,连个警告都没出现,很完美的代码,但是还是出bug了。
查了下百度,找资料查原因,最终找到了一段解释:
如果指定了T 类型为 Student 在自己的方法中是可以知道T 就是Student,但是如果到了封装方法中,T 并未真正转型,如果在其他真正支持泛型的语言里,如C#,上述代码是可以正确执行的。原因是JAVA实现的泛型是伪泛型。jdk1.5以后,在class文件里,method(List list)和method(List list)方法描述是一样的,只是额外新增了方法签名(Signature)和LocalVariableTypeTable保存对应方法的泛型信息。只有在赋值操作时才用到泛型信息,用法只是做了下强制转换而已。这也是通常所说的类型擦除。在整个父类方法执行的过程中,是不知道泛型信息的,而成功反序列化依赖外部调用方法的泛型信息。在不知道类型的情况下,只能解析为Object, Gson 用Map存储这类结果,就是报错内容。
3 解决错误
1:在封装方法中,不直接使用 Type ,修改封装方法
(1)RequestListener
在 RequestListener 接口中暴露 添加 Type getType();方法,在各自的调用方直接写明我这次需要的对象;
(2)封装 request 方法中
修改前:
Type type = new TypeToken<BaseEntity<T>>() { }.getType();
BaseEntity<T> baseEntity = new Gson().fromJson(serverStr, type);
修改后:
BaseEntity<T> baseEntity = new Gson().fromJson(serverStr, listener.getType());
(3)各自请求
private void getStudent() {
request(new RequestListener<Student>() {
@Override
public void onSuccess(Student data) {
}
@Override
public Type getType() {
return new TypeToken<BaseEntity<Student>>() {
}.getType();
}
});
}
private void getStudents() {
request(new RequestListener<List<Student>>() {
@Override
public void onSuccess(List<Student> data) {
}
@Override
public Type getType() {
return new TypeToken<BaseEntity<List<Student>>>() {
}.getType();
}
});
}
(4)修改后验证请求,后台数据还是原来的数据,就不做修改了。
2 更换解析框架 猝…
三:写后叨叨
作为一名优秀的 猿,就应该有勇于研究的勇气,在这里记录一下这次翻车事故现场,如嫌太菜,求轻喷…