Java如何优雅获取泛型类型
作者:莫那·鲁道
原文:http://thinkinjava.cn/2018/05/Java-%E5%A6%82%E4%BD%95%E8%8E%B7%E5%8F%96%E6%B3%9B%E5%9E%8B%E7%B1%BB%E5%9E%8B/
前言
在 Java 开发中,获取泛型这种操作虽不是很常用,但有时确实必须的,比如 将Json 字符串反序列化成对象的时候。今天就来介绍这个操作。
场景
假设我们定义了一个类,内部有一个数据结构,泛型为 T,当我们输入一个 Json 字符串,想把这个 Json 反序列化成对象,那么此时,我们就需要知道这个泛型的类型。
具体代码场景如下:
抽象父类(包含泛型):
abstract class Base<T extends Comparable<T>> {
T data;
public Base(String json) {
this.data = JsonUtil.toObject(json, deSerializable());
}}
我们想在该类中输入 Json,并将字符串反序列化成对象。比如下面这样:
/**
* 子类定义了父类
*/class Son extends Base<DataClass> {
public Son(String json) {
super(json);
}}/**
* 数据类型继承Comparable
*/class DataClass implements Comparable<DataClass> {
@Override
public int compareTo(DataClass o) {
return 0;
}}
上面的例子中,子类定义了泛型,但获取泛型类型是在父类。
所以,重点在 deSerializable() 方法的实现,我们需要一个 Class 让 Json 工具能够正常序列化。
如何实现?
先说结论:通过 Java 反射包的 ParameterizedType 工具获得泛型具体类型。
例如:下面的代码:
public static void main(String[] args) {
String json = JsonUtil.toJson(new DataClass());
Son s = new Son(json);
Type t = s.getClass().getGenericSuperclass();
if (t instanceof ParameterizedType) {
System.out.println(t);
// output: cn.think.in.java.clazz.loader.generics.Base<cn.think.in.java.clazz.loader.generics.DataClass>
for (Type type : ((ParameterizedType) t).getActualTypeArguments()) {
System.out.println(type);
//output: class cn.think.in.java.clazz.loader.generics.DataClass
}
}
}
首先我们将一个对象序列化成 Json 字符串,模拟外部输入。然后呢?创建一个子类对象,得到这个 Son 的 Class 。 关键地方来了,调用 getGenericSuperclass 方法,这个方法的作用是:返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的直接超类的 Type。
所以这里会得到一个 ParameterizedTypeImpl 类型的对象。注意:这个类是 Sun 包下的,不是开源的。该类有以下几个属性:
Type[] 数组就是该类(我们这里是父类)的泛型,rawType 是原始类型,即 Base 的 Class 类型。而 OwnerType 返回的则是 Base 类型。
然后呢,判断这个 t 是不是 ParameterizedType 接口的实现类。如果是,调用 getActualTypeArguments 方法,返回一个 Type数组,即上图的 actualTypeArguments 属性。
而返回的 Type 数组就是父类的泛型 Class。因为 Class 实现了 Type 接口。为什么是数组呢?因为每个类可以有多个泛型。
通过这样几行代码,我们就得到了泛型。当然,这种用法很少。
现在我们知道了如何得到泛型,那么,就将刚刚的场景中的问题解决。
实现反序列化方法:
private Class<T> deSerializable() {
Type type = getClass().getGenericSuperclass();
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
System.out.println(parameterizedType.getActualTypeArguments()[0]);
return (Class<T>) parameterizedType.getActualTypeArguments()[0];
}
throw new RuntimeException();
}
获取到当前类(Son)的泛型 Class,获取到泛型数组,返回第一个(因为我们只有一个泛型)泛型类型的 Class。 然后,使用 Json 工具传入 Json 字符串和 Class 类型并返回实体对象。
这样就能够保证编译不会错误,且高度灵活。
这里有一个地方需要注意:Java 的泛型是会在运行期擦除的,但并不总是擦除成 Object ,而是擦除到上限类型。 如果时获取接口的泛型则是调用 Class 的 getGenericInterfaces 方法得到接口集合的泛型。
总结
因为历史原因,Java 的泛型一直是个痛点,但无法避免,所以使用起来确实有点麻烦。但通过 Class 类的众多反射功能,我们还是能够处理泛型的问题。
我们今天使用反射得到了一个类的泛型,并在父类进行处理,成功的将一个字符串反序列化成一个对象。
- 二叉树的性质和常用操作代码集合
- linux学习第十五篇:磁盘格式化,磁盘挂载,手动增加swap空间
- 《Java程序设计基础》 第8章手记Part 2
- 备忘录模式
- 《Java程序设计基础》 第8章手记Part 1
- 你很有想法,跟我学做菜吧No.3
- 《数据结构》 定长顺序串常用操作代码集合
- 一斤代码深入理解系列(七):微信小程序中使用微信风格样式库-WeUI
- 餐厅老板要累疯了No.2
- linux学习第十九篇:压缩介绍,gzip,bzip2,xz压缩工具
- 区块链?黑人问号?NO.1
- linux学习第二十一篇:安装软件包的三种方法,rpm,yum工具用法,yum搭建本地仓库
- iOS使用自签名证书实现HTTPS请求
- 《Java程序设计基础》 第7章手记
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 【从0到1学算法】二分查找法
- Spring事务源码分析专题(二)Mybatis的使用及跟Spring整合原理分析
- IIC
- JVM系列之:Contend注解和false-sharing
- FFmpeg 实现视频 封装 与 解封装
- 万物皆可状态机
- ggplot2折线图展示美国和印度COVID-19单日新增确诊人数变化趋势
- JBrowse安装配置的一些教程
- 对不起,网上找的Redis分布式锁都有漏洞!
- 打卡群刷题总结0722——颜色分类
- 为什么Python没有main函数?
- 构建对象检测模型
- OpenCV4.4 + YOLOv4 真的可以运行了…..
- 基于分类任务的信号(EEG)处理
- 面试:如何决定使用 HashMap 还是 TreeMap?