文档章节

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

编走编想
 编走编想
发布于 2017/04/09 21:44
字数 1280
阅读 3.7K
收藏 228

精选30+云产品,助力企业轻松上云!>>>

前言

现在很多程序员都会在简历中写上精通 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 引入泛型擦除的原因,也自然能理解如何在运行时获取泛型信息了。

编走编想
粉丝 155
博文 129
码字总数 112781
作品 0
海淀
程序员
私信 提问
加载中
此博客有 15 条评论,请先登录后再查看。
Java-Se-泛型

上边界 & 下边界 上边界: 下边界: 泛型方法 语法: 注:T、S等,可以是任意的字母,表示先定义以便后面引用它。此时的“返回值”也可以用T或S来代替。 代码片段: 泛型擦除 哪些泛型信息会...

pandaaaa
2018/10/10
12
0
Java 泛型在实际开发中的应用

http://www.cnblogs.com/ldh-better/p/7127308.html 目录 一:泛型出现的背景 二: 泛型的语法使用 1:使用具体的泛型类型: 尖括号内带有具体的类型。可以限定这个Map的key和value只能是字符...

天涯爪哇岛
2018/11/18
0
0
Java 泛型在实际开发中的应用

Java 泛型在实际开发中的应用 https://www.cnblogs.com/ldh-better/p/7127308.html 目录 一:泛型出现的背景 二: 泛型的语法使用 1:使用具体的泛型类型: 尖括号内带有具体的类型。可以限定...

我风依旧
2018/09/17
0
0
Kotlin 泛型之类型擦除

一. Java 泛型的优点 泛型是 Java 5 的重要特性之一。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛...

fengzhizi715
2019/06/10
0
0
泛型与热更新方案Robust

Android平台出现了一些优秀的热更新方案,主要可以分为两类: 一类是基于multidex的热更新框架,包括Nuwa、Tinker等; 另一类就是native hook方案,如阿里开源的Andfix和Dexposed。 基于nat...

shareus
2018/05/19
0
0

没有更多内容

加载失败,请刷新页面

加载更多

docker镜像pull不下来最终解决方法

pull镜像wordpress下来,但是出现如下错误: # docker pull wordpress:latest Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled while ......

osc_x5cptzgg
26分钟前
5
0
雅猴的脚印——2020年下半年

雅猴的脚印——2020年下半年( 2020年上半年, 2019年下半年, 2019年上半年, 2018年及以前) ● 好忙 ……。(200704) 每个孩子都潜藏着强大的理解、思考、探索的能力,让孩子从小接触一点...

osc_1oqjcug0
27分钟前
10
0
代理损失函数(surrogate loss function)

Surrogate loss function,中文可以译为代理损失函数。当原本的loss function不便计算的时候,我们就会考虑使用surrogate loss function。   在二元分类问题中,假如我们有\(n\)个训练样本...

osc_9i00cx5n
28分钟前
11
0
Webapi管理和性能测试工具WebBenchmark

WebBenchmark是一款基于开源通讯组件Beetlex扩展的Webapi管理和性能测试工具(在技术上是beetlex webapi和vuejs的结合),在传统工具中一般管理工具缺乏性能压测能力或有性能测试的缺少管理功能...

osc_8eb8cps3
29分钟前
24
0
了解Java中的检查与未检查异常 - Understanding checked vs unchecked exceptions in Java

问题: Joshua Bloch in " Effective Java " said that 约书亚·布洛赫(Joshua Bloch)在《 有效的Java 》中说 Use checked exceptions for recoverable conditions and runtime exceptions......

fyin1314
29分钟前
14
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部