[转] Java 的泛型擦除和运行时泛型信息获取
原文链接 https://my.oschina.net/lifany/blog/875769
前言
现在很多程序员都会在简历中写上精通 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 的子类,泛型参数限定为了 String 和 Integer。
其实在“泛型擦除”一节,我们已经提到,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 引入泛型擦除的原因,也自然能理解如何在运行时获取泛型信息了。
[转] Java 的泛型擦除和运行时泛型信息获取的更多相关文章
- Java编译时常量和运行时常量
Java编译时常量和运行时常量 编译期常量指的就是程序在编译时就能确定这个常量的具体值. 非编译期常量就是程序在运行时才能确定常量的值,因此也称为运行时常量. 在Java中,编译期常量指的是用fina ...
- Java自定义注解和运行时靠反射获取注解
转载:http://blog.csdn.net/bao19901210/article/details/17201173/ java自定义注解 Java注解是附加在代码中的一些元信息,用于一些工具在编 ...
- Java异常-一般异常和运行时异常的区别
Java提供了两类主要的异常:runtime exception和checked exception.checked 异常也就是我们经常遇到的IO异常,以及SQL异常都是这种异常.对于这种异常, JA ...
- Java编译时多态和运行时多态
来源:https://blog.csdn.net/wendizhou/article/details/73733061 编译时多态:主要是方法的重载,通过参数列表的不同来区分不同的方法. 运行时多态: ...
- Java泛型和集合之泛型介绍
在声明一个接口和类的时候可以使用尖括号带有一个或者多个参数但是当你在声明属于一个接口或者类的变量的时候或者你在创建一个类实例的时候需要提供他们的具体类型.我们来看下下面这个例子 List<Str ...
- Java泛型擦除
Java泛型擦除: 什么是泛型擦除? 首先了解一下什么是泛型?我个人的理解:因为集合中能够存储随意类型的对象.可是集合中最先存储的对象类型一旦确定后,就不能在存储其它类型的对象了,否则,编译时不会报错 ...
- (一)关于java泛型的学习总结(泛型方法、泛型擦除)
目录概要 一.泛型方法 二.利用泛型方法的特性实现代码的简化 三. 关于泛型的擦除 四.无界通配符和原生类型区别 五.转型和警告 泛型 一般的类中的属性或方法的参数,只能使用具体的类型:要么是基本 ...
- 关于Java泛型"擦除"的一点思考
头次写博客,想说的东西不难,关于泛型的疑问,是前一阵在学习jackson中遇到的. 下面就把我所想到的.遇到的,分享出来. 泛型是JDK1.5后的一个特性,是一个参数类型的应用,可以将这个参数声明在类 ...
- java中的伪泛型---泛型擦除(不需要手工强转类型,却可以调用强转类型的方法)
Java集合如Map.Set.List等所有集合只能存放引用类型数据,它们都是存放引用类型数据的容器,不能存放如int.long.float.double等基础类型的数据. 使用反射可以破解泛型T类型 ...
随机推荐
- CentOS7编译安装mysql-5.6.43
Step 1:安装编译需要的软件和工具 [root@node-1 ~]# yum install gcc gcc-c++ cmake ncurses-devel bison Step 2:创建mysq ...
- H-Modify Minieye杯第十五届华中科技大学程序设计邀请赛现场赛
题面见 https://ac.nowcoder.com/acm/contest/700#question 题目大意是有n个单词,有k条替换规则(单向替换),每个单词会有一个元音度(单词里元音的个数)和 ...
- button的后台点击事件
在html元素加上runat,type就可以使用onserverclick创建后台事件<button runat='server' onserverclick='Btn_Click' type= ...
- git log命令常用参数集合
git log 查看 提交历史 默认不用任何参数的话,git log 会按提交时间列出所有的更新,最近的更新排在最上面. 常用的格式占位符写法及其代表的意义.选项 说明%H 提交对象(commit)的 ...
- 手把手教学在Springboot中搭建使用Guava cache,包教包会,不会我输一包辣条给你
guava cache使用简介 概述 缓存是日常开发中经常应用到的一种技术手段,合理的利用缓存可以极大的改善应用程序的性能. Guava官方对Cache的描述连接 缓存在各种各样的用例中非常有用.例 ...
- CSS3扫描动画效果
.swiper-animate { position: absolute; width: 100%; height: 100%; left:; top:; z-index:; background: ...
- java maven compiler设置默认1.8
方法一: <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupI ...
- canvas的使用方法
了解canvas:canvas标签是用作图形绘制,但是通过js脚本来实现的,canvas标签其实只是一个容器 ,最终实现绘制功能肯定是通过js脚本实现. 首先肯定要定义一个canvas标签当做容器 & ...
- LVS的DR模式测试案例<仅个人记录>
初始概念 大家都知道LVS,是章文嵩博士创建的,所以首先推一下主站吧!http://zh.linuxvirtualserver.org/ LVS集群分为三层结构: 负载调度器(load balance ...
- ACM(图论)——tarjan算法详解
---恢复内容开始--- tarjan算法介绍: 一种由Robert Tarjan提出的求解有向图强连通分量的线性时间的算法.通过变形,其亦可以求解无向图问题 桥: 割点: 连通分量: 适用问题: 求 ...