文档章节

Java 的泛型擦除和运行时泛型信息获取

编走编想
 编走编想
发布于 2017/04/09 21:44
字数 1280
阅读 2177
收藏 227
点赞 7
评论 15

前言

现在很多程序员都会在简历中写上精通 Java。但究竟怎样才算是精通 Java 呢?我觉得不仅要熟练掌握 Java 语法和 JDK 的使用,还需要对 Java 这门语言的各方面原理有深入的了解。除了像并发、JVM 等方面,以及软引用、弱引用等高级知识以外,其实很多我们每天接触到的 Java 特性里面也是另有乾坤。Java 5 引入的泛型便是其中之一。本文这里不谈泛型的使用以及泛型方法、泛型类的定义,这些东西很多书和文章都讲了。本文将介绍一下 Java 泛型的擦除和运行时泛型获取这两个看似矛盾的特性。

Java 的泛型擦除

程序员界有句流行的话,叫 talk is cheap, show me the code,所以话不多说,看代码。

代码一

Class c1 = new ArrayList<Integer>().getClass();
Class c2 = new ArrayList<String>().getClass(); 
System.out.println(c1 == c2);

/* Output
true
*/

ArrayList<Integer>ArrayList<String> 在编译的时候是完全不同的类型。你无法在写代码时,把一个 String 类型的实例加到 ArrayList<Integer> 中。但是在程序运行时,的的确确会输出true。

这就是 Java 泛型的类型擦除造成的,因为不管是 ArrayList<Integer> 还是 ArrayList<String>,在编译时都会被编译器擦除成了 ArrayList。Java 之所以要避免在创建泛型实例时而创建新的类,从而避免运行时的过度消耗。

代码二

List<Integer> list = new ArrayList<Integer>();
Map<Integer, String> map = new HashMap<Integer, String>();
System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
System.out.println(Arrays.toString(map.getClass().getTypeParameters()));

/* Output
[E]
[K, V]
*/

我们可能期望能够获得真实的泛型参数,但是仅仅获得了声明时泛型参数占位符。getTypeParameters 方法的 Javadoc 也是这么解释的:仅返回声明时的泛型参数。所以,通过 getTypeParamters 方法无法获得运行时的泛型信息。

运行泛型信息的获取

但是在有些场景中,我们还是需要获取泛型信息的。比如,在调用 HTTP 或 RPC 接口时,我们需要进行序列化和反序列的工作。

例如,我们通过一个 HTTP 接口接收如下的 JSON 数据

[{
    "name": "Stark",
    "nickName": "Iron Man"
},
{
    "name": "Rogers",
    "nickName": "Captain America"
}]

我们需要将其映射为 List<Avenger>

但是之前我们提到了泛型擦除,那我们所使用的 HTTP 或 RPC 框架是如何获取 List 中的泛型信息呢?

再看一段代码

Map<String, Integer> map = new HashMap<String, Integer>() {};

Type type = map.getClass().getGenericSuperclass();
ParameterizedType parameterizedType = ParameterizedType.class.cast(type);
for (Type typeArgument : parameterizedType.getActualTypeArguments()) {
    System.out.println(typeArgument.getTypeName());
}

/* Output
java.lang.String
java.lang.Integer
*/

上面这段代码展示了如何获取 map 这个实例所对应类的泛型信息。显然,这次我们成功获得了其中泛型参数信息。有了这些泛型参数,上面所提到的序列化和反序列化工作就是可能的了。

那为什么之前不可以,而这次可以了呢?请注意一个细节

上一节的变量声明

Map<Integer, String> map = new HashMap<Integer, String>();

本节的变量声明

Map<String, Integer> map = new HashMap<String, Integer>() {};

其中最关键的差别是本节的变量声明多了一对大括号。有一定 Java 基础的同学都能看出本节的变量声明其实是创建了一个匿名内部类。这个类是 HashMap 的子类,泛型参数限定为了 StringInteger

其实在“泛型擦除”一节,我们已经提到,Java 引入泛型擦除的原因是避免因为引入泛型而导致运行时创建不必要的类。那我们其实就可以通过定义类的方式,在类信息中保留泛型信息,从而在运行时获得这些泛型信息。

简而言之,Java 的泛型擦除是有范围的,即类定义中的泛型是不会被擦除的。

框架中的应用

其实很多框架就是使用类定义中的泛型不会被擦除这个特性,实现了相应的功能。

例如,Spring Web 模块的 RestTemplate,我们可以使用如下写法:

ResponseEntity<YourType> responseEntity = restTemplate.exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference<YourType>() {});

其中的 new ParameterizedTypeReference<YourType>() {} 就是通过定义一个匿名内部类的方式来获得泛型信息,从而进行反序列化的工作。

总结

Java 泛型擦除是 Java 泛型中的一个重要特性,其目的是避免过多的创建类而造成的运行时的过度消耗。所以,想 ArrayList<Integer>ArrayList<String> 这两个实例,其类实例是同一个。

但很多情况下我们又需要在运行时获得泛型信息,那我们可以通过定义类的方式(通常为匿名内部类,因为我们创建这个类只是为了获得泛型信息)在运行时获得泛型参数,从而满足例如序列化、反序列化等工作的需要。

只要理解了 Java 引入泛型擦除的原因,也自然能理解如何在运行时获取泛型信息了。

© 著作权归作者所有

共有 人打赏支持
编走编想
粉丝 135
博文 120
码字总数 107958
作品 0
海淀
程序员
加载中

评论(15)

编走编想
编走编想

引用来自“令狐少侠sky”的评论

java 引入泛型擦除的原因,是要兼容1.5之前,没有使用泛型的库;又在误导群众了。知道了这个原因,才可以知道怎么弥补擦除带来的问题,

引用来自“编走编想”的评论

Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead. https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

引用来自“问津少年”的评论

准确来说,Java的泛型是使用擦除来实现的,擦除并非能称之为java的语言特性,它只是实现泛型的一种方案折中,oracle官网说的没有问题,但是并不是因为这个导致泛型需要使用擦除实现,很大程度上是为了满足Java SE5之前的兼容,在不破坏之前类库的基础上,实现非泛化代码到泛化代码的转变。

文章并没有说到使用匿名类的原因,为什么它的类型不会被擦除? 这是边界问题,泛型类型参数会擦除到它的第一个边界,当示例中使用匿名类时,使用的是HashMap的子类,也即 T extends HashMap,那么类型擦除也就会擦除到它的第一个边界HashMap,所以运行时的类型信息是具备的。
介绍的很详细。我之前也一直认为反省擦除是为了保持兼容性,但后来有一点不太明白,如果按照 Java 5 之后的版本编译,那是否还需要因为兼容性的问题而进行泛型擦除。我个人看法是不需要,但没有找到相关文章详细解释,所以既不能证实,也不能证伪。故这个问题我保持中立,也就没提到兼容性一说
问津已非少年
问津已非少年

引用来自“令狐少侠sky”的评论

java 引入泛型擦除的原因,是要兼容1.5之前,没有使用泛型的库;又在误导群众了。知道了这个原因,才可以知道怎么弥补擦除带来的问题,

引用来自“编走编想”的评论

Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead. https://docs.oracle.com/javase/tutorial/java/generics/erasure.html
OSC太坑了,不能回复自己。。

上面写的是 "HashMap < String, Integer >",不知道怎么的尖括号消失了,消失了。。
问津已非少年
问津已非少年

引用来自“令狐少侠sky”的评论

java 引入泛型擦除的原因,是要兼容1.5之前,没有使用泛型的库;又在误导群众了。知道了这个原因,才可以知道怎么弥补擦除带来的问题,

引用来自“编走编想”的评论

Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead. https://docs.oracle.com/javase/tutorial/java/generics/erasure.html
准确来说,Java的泛型是使用擦除来实现的,擦除并非能称之为java的语言特性,它只是实现泛型的一种方案折中,oracle官网说的没有问题,但是并不是因为这个导致泛型需要使用擦除实现,很大程度上是为了满足Java SE5之前的兼容,在不破坏之前类库的基础上,实现非泛化代码到泛化代码的转变。

文章并没有说到使用匿名类的原因,为什么它的类型不会被擦除? 这是边界问题,泛型类型参数会擦除到它的第一个边界,当示例中使用匿名类时,使用的是HashMap的子类,也即 T extends HashMap,那么类型擦除也就会擦除到它的第一个边界HashMap,所以运行时的类型信息是具备的。
谁说我不是会员
谁说我不是会员
不错的文章
编走编想
编走编想

引用来自“令狐少侠sky”的评论

java 引入泛型擦除的原因,是要兼容1.5之前,没有使用泛型的库;又在误导群众了。知道了这个原因,才可以知道怎么弥补擦除带来的问题,
Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead. https://docs.oracle.com/javase/tutorial/java/generics/erasure.html
陈俞霖
陈俞霖

引用来自“xlabs”的评论

引用来自“陈俞霖”的评论

写了那么多全是废话啊 完全没解释范型没解释边界没解释协逆变

@陈俞霖你来写一篇呀,就知道喷@陈俞霖

别误会,我没在喷啊,我就是常常简历写精通Java的那个,被嘲讽了
陈俞霖
陈俞霖

引用来自“xlabs”的评论

引用来自“陈俞霖”的评论

写了那么多全是废话啊 完全没解释范型没解释边界没解释协逆变

@陈俞霖你来写一篇呀,就知道喷@陈俞霖

你又不是作者激动什么,文章前言说得这么拽,还准备拜读的...获取个类型也算精通Java啊
心浪微勃
心浪微勃

引用来自“陈俞霖”的评论

写了那么多全是废话啊 完全没解释范型没解释边界没解释协逆变

@陈俞霖你来写一篇呀,就知道喷@陈俞霖
陈俞霖
陈俞霖
写了那么多全是废话啊 完全没解释范型没解释边界没解释协逆变
令狐少侠sky
令狐少侠sky
java 引入泛型擦除的原因,是要兼容1.5之前,没有使用泛型的库;又在误导群众了。知道了这个原因,才可以知道怎么弥补擦除带来的问题,
Kotlin泛型-你可能需要知道这些

本博文主要讲解一些Kotlin泛型的问题,中间会对比穿插Java泛型。 1. 泛型类型参数 1.1 形式 我们使用泛型的形式无非是类、借口、方法几种,我们先看两个例子。 1.2 声明泛型类 和Java一样,我...

24K男
04/21
0
0
【转】Java语法糖的味道:泛型与类型擦除

泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类...

mj4738
2011/11/29
0
0
Java深度历险 Java泛型

Java泛型(generics)是JDK 5中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。声明的类型参数在使用时用具体的类型来替换。泛型最主要的应用是在JDK 5中的新集...

SeanCai
2011/03/03
0
2
Kotlin 泛型 VS Java 泛型

建议先阅读我的上一篇文章 -- Java 泛型 和 Java 泛型一样,Kotlin 泛型也是 Kotlin 语言中较难理解的一个部分。Kotlin 泛型的本质也是参数化类型,并且提供了编译时强类型检查,实际上也是伪...

JohnnyShieh
06/11
0
0
java 泛型-擦除erasure

类型擦除(type erasure)。 Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候...

110hxl
2014/02/11
0
0
Java 泛型,你了解类型擦除吗?

泛型,一个孤独的守门者。 大家可能会有疑问,我为什么叫做泛型是一个守门者。这其实是我个人的看法而已,我的意思是说泛型没有其看起来那么深不可测,它并不神秘与神奇。泛型是 Java 中一个...

xiaomin0322
06/06
0
0
SharePreference保存对象与集合

转载https://blog.csdn.net/qq_21937107/article/details/79735236 在Android中我们经常需要持久化保存数据,这时可以使用文件,SharePreference,以及SQLite等。如果我想保存一个对象呢?有...

sinat_17775997
04/23
0
0
【J2SE】JAVA语法糖之-伪泛型

JAVA语法糖之-伪泛型 泛型即参数化类型 首先看一个泛型例子: public static void main(String[] args) {List<String> strList = new ArrayList<String>();strList.add("generic test!");fo......

磊神Ray
2011/09/28
0
0
Java 容器 & 泛型:六、容器讲到为什么要使用泛型

Writer:BYSocket(泥沙砖瓦浆木匠) 微博:BYSocket 豆瓣:BYSocket ArrayList是集合类中无处不在的,泛型也是,泛型对集合类尤其有用。但是为啥要使用泛型?理解好了这个问题可以帮助理解相...

泥沙砖瓦浆木匠
2015/05/07
0
3
GSON使用笔记(3) -- 如何反序列化出List

GSON使用笔记(3) -- 如何反序列化出List 时间 2014-06-26 17:57:06 CSDN博客原文 http://blog.csdn.net/zxhoo/article/details/34856061 本文通过3个问题来讨论如何使用 GSON 把JSON反序列...

thinkyoung
2015/03/24
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

NNS域名系统之域名竞拍

0x00 前言 其实在官方文档中已经对域名竞拍的过程有详细的描述,感兴趣的可以移步http://doc.neons.name/zh_CN/latest/nns_protocol.html#id30 此处查阅。 我这里主要对轻钱包开发中会用到的...

暖冰
今天
0
0
32.filter表案例 nat表应用 (iptables)

10.15 iptables filter表案例 10.16/10.17/10.18 iptables nat表应用 10.15 iptables filter表案例: ~1. 写一个具体的iptables小案例,需求是把80端口、22端口、21 端口放行。但是,22端口我...

王鑫linux
今天
0
0
shell中的函数&shell中的数组&告警系统需求分析

20.16/20.17 shell中的函数 20.18 shell中的数组 20.19 告警系统需求分析

影夜Linux
今天
0
0
Linux网络基础、Linux防火墙

Linux网络基础 ip addr 命令 :查看网口信息 ifconfig命令:查看网口信息,要比ip addr更明了一些 centos 7默认没安装ifconfig命令,可以使用yum install -y net-tools命令来安装。 ifconfig...

李超小牛子
今天
1
0
[机器学习]回归--Decision Tree Regression

CART决策树又称分类回归树,当数据集的因变量为连续性数值时,该树算法就是一个回归树,可以用叶节点观察的均值作为预测值;当数据集的因变量为离散型数值时,该树算法就是一个分类树,可以很...

wangxuwei
昨天
1
0
Redis做分布式无锁CAS的问题

因为Redis本身是单线程的,具备原子性,所以可以用来做分布式无锁的操作,但会有一点小问题。 public interface OrderService { public String getOrderNo();} public class OrderRe...

算法之名
昨天
10
0
143. Reorder List - LeetCode

Question 143. Reorder List Solution 题目大意:给一个链表,将这个列表分成前后两部分,后半部分反转,再将这两分链表的节点交替连接成一个新的链表 思路 :先将链表分成前后两部分,将后部...

yysue
昨天
1
0
数据结构与算法1

第一个代码,描述一个被称为BankAccount的类,该类模拟了银行中的账户操作。程序建立了一个开户金额,显示金额,存款,取款并显示余额。 主要的知识点联系为类的含义,构造函数,公有和私有。...

沉迷于编程的小菜菜
昨天
1
0
从为什么别的队伍总比你的快说起

在机场候检排队的时候,大多数情况下,别的队伍都要比自己所在的队伍快,并常常懊悔当初怎么没去那个队。 其实,最快的队伍只能有一个,而排队之前并不知道那个队快。所以,如果有六个队伍你...

我是菜鸟我骄傲
昨天
1
0
分布式事务常见的解决方案

随着互联网的发展,越来越多的多服务相互之间的调用,这时候就产生了一个问题,在单项目情况下很容易实现的事务控制(通过数据库的acid控制),变得不那么容易。 这时候就产生了多种方案: ...

小海bug
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部