
Java中含有泛型的 JSON 反序列化问题
一、背景
今天无聊之余提了一个问题,涉及的示例大致如下:
例子中使用fastjson 的类库。
为什么 IDEA 会给出下面的警告,该如何解决?
有些同学说直接使用抑制注解,抑制掉这个警告就好了。
抑制掉警告就可以了????
二、分析
2.1 事出诡异必有妖
IDEA 不会无缘无故给出警告提示,警告的原因上图已经给出。
把不带泛型的 List 赋值给带泛型的 List, Java 编译器并不知道右侧返回不带泛型的实际 List 是否符合带泛型的 List 约束。
和下面的例子非常类似:
将 first 赋值给 third 时,不能保证 first 元素符合 List的约束,即列表中全是 String。
如果你执行上述代码,会发现没有报错,哈哈。
但是如果你使用 foreach 循环或者迭代器取 String 循环时会发生类型转换异常。
类型转换异常?
我们使用 IDEA 的 jclasslib 反编译插件,得到 main 函数的 Code 如下:
从 42 到76 行 对应 foreach 循环的逻辑,可以看出底层使用 List 的迭代器进行遍历,取出每个元素后强转为 String 类型,存储到局部变量表索引为 4 的位置,然后进行打印。
如果对反编译不熟悉可以去 target 目录,双击编译后的class 文件,使用 IDEA 自带的插件进行反编译:
印证了上述说法,显然在 String each = (String)var3.next(); 这里出现了类型转换异常。
三、解决之道
3.1 猜想验证
我们猜测是不是可以通过某种途径将泛型作为参数传给 fastjson, 让 fastjson 某个返回值是带泛型的,从而解决这个告警呢?
显然我们要去源码中寻找, 在 JSONObject 类中找到了下面的方法:
该函数的注释上还贴心地给出了相关用法,因此我们改造下:
警告解除了。
所以大功告成?
难道上述做法仅仅是为了消除一个警告,满足强迫症们的心愿而已吗??
且慢,我们看下面的例子:
大家执行上述例子会出现类型转换异常!
Exception in thread “main” java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.chujianyun.common.json.User at com.chujianyun.common.json.JsonGenericDemo.main(JsonGenericDemo.java:26)
有了第二部分的分析,大家可能就可以比较容易地想到
JSONObject.parseObject(jsonString, List.class)
构造出来的 List 存放的是 JSONObject 元素, foreach 循环底层使用迭代器遍历每个元素并强转为 User 类型是报类型转换异常。
那么为啥 fastjson 不能帮我们转换为 List<User>
类型呢?
有人说“由于泛型擦除,没有泛型信息,所以无法逆向构造回原有类型”。
其实看下 JSONObject.parseObject(jsonString, List.class);
第一个参数是字符串,第二个参数是 List.class。压根就没有提供泛型信息给 fastjson。
作为这个工具函数本身,怎么猜得到要 List 里面究竟该存放啥类型呢?
因此如果能够通过某种途径,告诉它泛型的类型,就可以帮助你反序列化成真正的类型。
使用 JSONObject.parseObject(jsonString, new TypeReference<List<User>>() { });
即可。
因此我们使用 TypeReference 并不仅仅是为了消除警告,而是为了告知 fastjson 泛型的具体类型,正确反序列化泛型的类型。
那么底层原理是啥呢?我们看下com.alibaba.fastjson.TypeReference#TypeReference()
通过代码和注释我们了解到:
创建一个空的匿名子类。将类型参数嵌入到匿名继承结构中,即使运行时类型擦除也可以重建。
再回到 parseObject 函数,可以看到底层用的就是这个 type。
3.2 举一反三
很多其他框架也会采用类似的方法来获取泛型类型。
大家可以看看其他 gson 类库
看看其中的 com.google.gson.reflect.TypeToken
类,是不是似曾相识呢?
此外,如果我们自己除了 JSON反序列化场景之外也有类似获取泛型参数的需求,是不是也可以采用类似的方法呢?
四、总结
希望大家能够重视 IDEA 的警告。
遇到问题能够从更合理的角度思考,了解问题的本质。
学习一个问题可以尝试举一反三,活学活用。
免责声明
本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,本平台和发布者不为此承担任何责任。