栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

含有泛型的 JSON 反序列化

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

含有泛型的 JSON 反序列化

一、背景

今天无聊之园提了一个问题,涉及的示例大致如下:

 public static void main(String[] args) {

 String jsonString = "["a","b"]";

 List list = JSONObject.parseObject(jsonString, List.class);

 System.out.println(list);

}

为什么 IDEA 会给出下面的警告,该如何解决?

有些同学说直接使用抑制注解,抑制掉这个警告就好了。

抑制掉警告就可以了????

二、分析 2.1 事出诡异必有妖

IDEA 不会无缘无故给出警告提示,警告的原因上图已经给出。

把不带泛型的 List 赋值给带泛型的 List, Java 编译器并不知道右侧返回不带泛型的实际 List 是否符合带泛型的 List 约束。

和下面的例子非常类似:

public static void main(String[] args) {

 List first = new ArrayList();

 first.add(1);

 first.add("2");

 first.add('3');

​

 // 提示上述警告

 List third = first;

 System.out.println(third);

 }

将 first 赋值给 third 时,不能保证 first 元素符合 List的约束,即列表中全是 String。

如果你执行上述代码,会发现没有报错,哈哈。

但是如果你使用 foreach 循环或者迭代器取 String 循环时会发生类型转换异常。

public static void main(String[] args) {

 List first = new ArrayList();

 first.add(1);

 first.add("2");

 first.add('3');

​

 List third = first;

 for (String each : third) { // 类型转换异常

 System.out.println(each);

 }

 }

类型转换异常?

我们使用 IDEA 的 jclasslib 反编译插件,得到 main 函数的 Code 如下:

0 new #2

3 dup

4 invokespecial #3

7 astore_1

8 aload_1

9 iconst_1

10 invokestatic #4

13 invokeinterface #5 count 2

18 pop

19 aload_1

20 ldc #6 <2>

22 invokeinterface #5 count 2

27 pop

28 aload_1

29 bipush 51

31 invokestatic #7

34 invokeinterface #5 count 2

39 pop

40 aload_1

41 astore_2

42 aload_2

43 invokeinterface #8 count 1

48 astore_3

49 aload_3

50 invokeinterface #9 count 1

55 ifeq 79 (+24)

58 aload_3

59 invokeinterface #10 count 1

64 checkcast #11

67 astore_4

69 getstatic #12

72 aload_4

73 invokevirtual #13

76 goto 49 (-27)

79 return

从 42 到76 行 对应 foreach 循环的逻辑,可以看出底层使用 List 的迭代器进行遍历,取出每个元素后强转为 String 类型,存储到局部变量表索引为 4 的位置,然后进行打印。

如果对反编译不熟悉可以去 target 目录,双击编译后的class 文件,使用 IDEA 自带的插件进行反编译:

//

// Source code recreated from a .class file by IntelliJ IDEA

// (powered by Fernflower decompiler)

//

​

package com.chujianyun.common.json;

​

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

​

public class JsonGenericDemo {

 public JsonGenericDemo() {

 }

​

 public static void main(String[] args) {

 List first = new ArrayList();

 first.add(1);

 first.add("2");

 first.add('3');

 List third = first;

 Iterator var3 = first.iterator();

​

 while(var3.hasNext()) {

 String each = (String)var3.next();

 System.out.println(each);

 }

​

 }

}

印证了上述说法,显然在 String each = (String)var3.next(); 这里出现了类型转换异常。

三、解决之道 3.1 猜想验证

我们猜测是不是可以通过某种途径将泛型作为参数传给 fastjson, 让 fastjson 某个返回值是带泛型的,从而解决这个告警呢?

显然我们要去源码中寻找, 在 JSonObject 类中找到了下面的方法:



 @SuppressWarnings("unchecked")

 public static  T parseObject(String text, TypeReference type, Feature... features) {

 return (T) parseObject(text, type.type, ParserConfig.global, DEFAULT_PARSER_FEATURE, features);

 }

该函数的注释上还贴心地给出了相关用法,因此我们改造下:

 public static void main(String[] args) {

 String jsonString = "["a","b"]";

 List list = JSONObject.parseObject(jsonString, new TypeReference>() {

 });

 System.out.println(list);

}

警告解除了。

所以大功告成?

难道上述做法仅仅是为了消除一个警告,满足强迫症们的心愿而已吗??

且慢,我们看下面的例子:

import lombok.Data;

@Data
public class User {
 private Long id;

 private String name;
}
import com.alibaba.fastjson.JSON;

import com.alibaba.fastjson.JSONObject;

import java.util.ArrayList;

import java.util.List;

public class JsonGenericDemo {

 public static void main(String[] args) {

 // 构造数据

 User user = new User();

 user.setId(0L);

 user.setName("tom");

​

 List users = new ArrayList<>();

 users.add(user);

 // 转为JSON字符串
 String jsonString = JSON.toJSONString(users);


 // 反序列化
 List usersGet = JSONObject.parseObject(jsonString, List.class);

 for (User each : usersGet) {
 System.out.println(each);
 }
 }
}

大家执行上述例子会出现类型转换异常!

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 类型呢?

有人说“由于泛型擦除,没有泛型信息,所以无法逆向构造回原有类型”。

其实看下 JSONObject.parseObject(jsonString, List.class); 第一个参数是字符串,第二个参数是 List.class。

作为这个工具函数本身,怎么猜得到要 List 里面究竟该存放啥类型呢?

因此如果能够通过某种途径,告诉它泛型的类型,就可以帮助你反序列化成真正的类型。

使用 JSONObject.parseObject(jsonString, new TypeReference>() { }); 即可。

因此我们使用 TypeReference 并不仅仅是为了消除警告,而是为了告知 fastjson 泛型的具体类型,正确反序列化泛型的类型

那么底层原理是啥呢?我们看下 com.alibaba.fastjson.TypeReference#TypeReference()



 protected TypeReference(){

 // 获取父类的 Type

 Type superClass = getClass().getGenericSuperclass();

​

 // 如果父类是参数化类型,会返回 java.lang.reflect.ParameterizedType

 // 调用 getActualTypeArguments 获取实际类型的数组 并拿到第一个

 Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];

​

 // 缓存中有优先取缓存,没有则存入并设置

 Type cachedType = classTypeCache.get(type);

 if (cachedType == null) {

 classTypeCache.putIfAbsent(type, type);

 cachedType = classTypeCache.get(type);

 }

 this.type = cachedType;
 }

通过代码和注释我们了解到:

创建一个空的匿名子类。将类型参数嵌入到匿名继承结构中,即使运行时类型擦除也可以重建。

再回到 parseObject 函数,可以看到底层用的就是这个 type。

 

 @SuppressWarnings("unchecked")

 public static  T parseObject(String text, TypeReference type, Feature... features) {

 return (T) parseObject(text, type.type, ParserConfig.global, DEFAULT_PARSER_FEATURE, features);

 }
3.2 举一反三

很多其他框架也会采用类似的方法来获取泛型类型。

大家可以看看其他 gson 类库


  com.google.code.gson
  gson
  2.8.6

中的 com.google.gson.reflect.TypeToken 类,是不是似曾相识呢?

此外,如果我们自己除了 JSON反序列化场景之外也有类似获取泛型参数的需求,是不是也可以采用类似的方法呢?

四、总结

希望大家能够重视 IDEA 的警告。

遇到问题能够从更合理的角度思考,了解问题的本质。

学习一个问题可以尝试举一反三,活学活用。


想了解更多开发和避坑技巧,经验,学习方法少走弯路,

欢迎关注本人的慕课专栏:

再学经典:《Effective Java》独家解析

解锁大厂思维:剖析《阿里巴巴 Java 开发手册》

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号