详解Gson的TypeToken原理
概要
借助对TypeToken原理的分析,加强对泛型擦除的理解,使得我们能够知道什么时候,通过什么方式可以获取到泛型的类型。
泛型擦除
众所周知,Java的泛型只在编译时有效,到了运行时这个泛型类型就会被擦除掉,即List<String>
和List<Integer>
在运行时其实都是List<Object>
类型。
为什么选择这种实现机制?不擦除不行么? 在Java诞生10年后,才想实现类似于C++模板的概念,即泛型。Java的类库是Java生态中非常宝贵的财富,必须保证向后兼容(即现有的代码和类文件依旧合法)和迁移兼容(泛化的代码和非泛化的代码可互相调用)基于上面这两个背景和考虑,Java设计者采取了"类型擦除"这种折中的实现方式。
同时正正有这个这么"坑"的机制,令到我们无法在运行期间随心所欲的获取到泛型参数的具体类型。
TypeToken
使用
使用过Gson的同学都知道在反序列化时需要定义一个TypeToken类型,像这样
private Type type = new TypeToken<List<Map<String, Foo>>>(){}.getType();
//调用fromJson方法时把type传过去,如果type的类型和json保持一致,则可以反序列化出来
gson.fromJson(json, type);
三个问题
- 为什么要用TypeToken来定义反序列化的类型?
正如上面说的,如果直接把List<Map<String, Foo>>的类型传过去,但是因为运行时泛型被擦除了,所以得到的其实是
List<Object>
,那么后面的Gson就不知道要转成Map<String, Foo>
类型了,这时Gson会默认转成LinkedTreeMap类型。 - 为什么带有大括号{}? 这个大括号就是精髓所在。大家都知道,在Java语法中,在这个语境,{}是用来定义匿名类,这个匿名类是继承了TypeToken类,它是TypeToken的子类。
- 为什么要通过子类来获取泛型的类型?
这是TypeToken能够获取到泛型类型的关键,这是一个巧妙的方法。
这个想法是这样子的,既然像
List<String>
这样中的泛型会被擦除掉,那么我用一个子类SubList extends List<String>
这样的话,在JVM内部中会不会把父类泛型的类型给保存下来呢?我这个子类需要继承的父类的泛型都是已经确定了的呀,果然,JVM是有保存这部分信息的,它是保存在子类的Class信息中,具体看: https://stackoverflow.com/questions/937933/where-are-generic-types-stored-in-java-class-files 那么我们怎么获取这部分信息呢? 还好,Java有提供API出来:
Type mySuperClass = foo.getClass().getGenericSuperclass(); Type type = ((ParameterizedType)mySuperClass).getActualTypeArguments()[0]; System.out.println(type);
分析一下这段代码,Class类的getGenericSuperClass()方法的注释是:
Returns the Type representing the direct superclass of the entity (class, interface, primitive type or void) represented by thisClass. If the superclass is a parameterized type, the Type object returned must accurately reflect the actual type parameters used in the source code. The parameterized type representing the superclass is created if it had not been created before. See the declaration of ParameterizedType for the semantics of the creation process for parameterized types. If thisClass represents either theObject class, an interface, a primitive type, or void, then null is returned. If this object represents an array class then theClass object representing theObject class is returned
概括来说就是对于带有泛型的class,返回一个ParameterizedType对象,对于Object、接口和原始类型返回null,对于数 组class则是返回Object.class。ParameterizedType是表示带有泛型参数的类型的Java类型,JDK1.5引入了泛型之 后,Java中所有的Class都实现了Type接口,ParameterizedType则是继承了Type接口,所有包含泛型的Class类都会实现 这个接口。
自己调试一下就知道它返回的是什么了。
原理
核心的方法就是刚刚说的那两句,剩下的就很简单了。 我们看看TypeToken的getType方法
public final Type getType() {
//直接返回type
return type;
}
看type的初始化
//注意这里用了protected关键字,限制了只有子类才能访问
protected TypeToken() {
this.type = getSuperclassTypeParameter(getClass());
this.rawType = (Class<? super T>) $Gson$Types.getRawType(type);
this.hashCode = type.hashCode();
}
//getSuperclassTypeParameter方法
//这几句就是上面的说到
static Type getSuperclassTypeParameter(Class<?> subclass) {
Type superclass = subclass.getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing type parameter.");
}
ParameterizedType parameterized = (ParameterizedType) superclass;
//这里注意一下,返回的是Gson自定义的,在$Gson$Types里面定义的TypeImpl等,这个类都是继承Type的。
return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
}
总结
在了解原理之后,相信大家都知道怎么去获取泛型的类型了。
参考资料
https://www.cnblogs.com/doudouxiaoye/p/5688629.html
- 第五章:Shiro的授权(Authorization)——深入浅出学Shiro细粒度权限开发框架
- 第六章:Shiro的Realms——深入浅出学Shiro细粒度权限开发框架
- 第八章:Shiro和Spring的集成——深入浅出学Shiro细粒度权限开发框架
- 第九章:Shiro的Web——深入浅出学Shiro细粒度权限开发框架
- 第十章:Shiro的Cache——深入浅出学Shiro细粒度权限开发框架
- Appboy基于MongoDB的数据密集型实践
- 微信企业号登录授权Java实现获取员工userid根据userid换openid
- 微信支付-微信红包Java版本
- Universe入门
- 分享一款值得分享的写作工具
- 微信二次开发Java自定义菜单事件实现
- 微信OAuth授权获取用户OpenId-JAVA(个人经验)
- 【手写文字识别】-JavaAPI示例代码
- 【Python3-API】情感倾向分析示例代码
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Kafka集群监控、安全机制与最佳实践
- PyQt5 技术篇-设置输入框的placeholder方法,Qt Designer设置Line Edit、Text Edit编辑框的placeholder
- Kafka集群搭建及必知必会
- Python 技术篇-打开指定文件夹、目录、路径方法,运行指定文件演示
- Python 技巧篇-同一个方法多次引用不同效果功能实现,可选参数设置方法
- Kafka核心API——Connect API
- PyQt5 技术篇-设置alignment对齐方式。Qt Designer设置文本对齐方式。居中、左对齐、右对齐、上对齐、下对齐。
- Nginx部署Vue项目以及解决刷新页面404
- Kafka核心API——Stream API
- Kafka核心API——Consumer消费者
- Kafka核心API——Producer生产者
- Linux Lab v0.5 正式发布,功能强大,用法简单
- Kafka核心API——AdminClient API
- PyQt5 技术篇-调用颜色对话框(QColorDialog)获取颜色,调色板的调用。
- Kafka的安装与配置