原文链接 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 的泛型擦除和运行时泛型信息获取的更多相关文章

  1. Java编译时常量和运行时常量

    Java编译时常量和运行时常量 编译期常量指的就是程序在编译时就能确定这个常量的具体值. 非编译期常量就是程序在运行时才能确定常量的值,因此也称为运行时常量. 在Java中,编译期常量指的是用fina ...

  2. Java自定义注解和运行时靠反射获取注解

    转载:http://blog.csdn.net/bao19901210/article/details/17201173/ java自定义注解 Java注解是附加在代码中的一些元信息,用于一些工具在编 ...

  3. Java异常-一般异常和运行时异常的区别

    Java提供了两类主要的异常:runtime exception和checked exception.checked 异常也就是我们经常遇到的IO异常,以及SQL异常都是这种异常.对于这种异常, JA ...

  4. Java编译时多态和运行时多态

    来源:https://blog.csdn.net/wendizhou/article/details/73733061 编译时多态:主要是方法的重载,通过参数列表的不同来区分不同的方法. 运行时多态: ...

  5. Java泛型和集合之泛型介绍

    在声明一个接口和类的时候可以使用尖括号带有一个或者多个参数但是当你在声明属于一个接口或者类的变量的时候或者你在创建一个类实例的时候需要提供他们的具体类型.我们来看下下面这个例子 List<Str ...

  6. Java泛型擦除

    Java泛型擦除: 什么是泛型擦除? 首先了解一下什么是泛型?我个人的理解:因为集合中能够存储随意类型的对象.可是集合中最先存储的对象类型一旦确定后,就不能在存储其它类型的对象了,否则,编译时不会报错 ...

  7. (一)关于java泛型的学习总结(泛型方法、泛型擦除)

    目录概要 一.泛型方法 二.利用泛型方法的特性实现代码的简化 三. 关于泛型的擦除 四.无界通配符和原生类型区别 五.转型和警告   泛型 一般的类中的属性或方法的参数,只能使用具体的类型:要么是基本 ...

  8. 关于Java泛型"擦除"的一点思考

    头次写博客,想说的东西不难,关于泛型的疑问,是前一阵在学习jackson中遇到的. 下面就把我所想到的.遇到的,分享出来. 泛型是JDK1.5后的一个特性,是一个参数类型的应用,可以将这个参数声明在类 ...

  9. java中的伪泛型---泛型擦除(不需要手工强转类型,却可以调用强转类型的方法)

    Java集合如Map.Set.List等所有集合只能存放引用类型数据,它们都是存放引用类型数据的容器,不能存放如int.long.float.double等基础类型的数据. 使用反射可以破解泛型T类型 ...

随机推荐

  1. js判断一个变量是数组还是对象

    判断变量是数组还是对象,使用Object.prototype.toString.call(),兼容性好,切勿使用typeof来判断对象或者数组,因为typeof得到的都是object: functio ...

  2. innodb 关键特性(insert buffer)

    一.insert buffer 性能改善 insert buffer和数据页一样,也是物理页的一个组成部分. 在innodb存储引擎中,主键是行唯一的标识符.通常应用程序中行记录的插入顺序是按照主键递 ...

  3. Opencv-Python学习笔记(一)

    学习和研究计算机视觉,必然绕不开OpenCV. 于是我下载了它的C++源码,用cmake编译遇到一些错误. 然后结合网上一些帖子看源码看了好几天,发现有点不知从何处入手. 于是准备从其python版本 ...

  4. redis缓存雪崩、穿透、击穿概念及解决办法

    缓存雪崩 对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机.缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据 ...

  5. 轻量级富文本编辑器wangEditor

    开发公司一个系统的时候需要一个富文本编辑器,找了几个,最后选择这个,蛮不错的. 百度搜索wangEditor,进入官网根据所介绍的使用进行开发就可以了,很不错的一个工具.

  6. python入门day01

      一.编程和编程语言 电脑的基本原理: #计算机通过高低电流表示二进制数的1和0,所以计算机识别的是电压的高低,准确地说是用电压表示的各种数据,即数字信号;其他的物理量必须通过传感器等设备转换成数字 ...

  7. 7.Redis主线程阻塞原因

    7.Redis主线程阻塞原因7.1 发现阻塞7.2 内在原因7.2.1 API或数据结构使用不合理7.2.2 CPU饱和7.2.3 持久化阻塞7.3 外在原因7.3.1 CPU竞争7.3.2 内存交换 ...

  8. LR 11 代理录制步骤

    1.新一个HTTP录制,到下图步骤 按下图说明设置信息,点击update 选择rl里面的代理exe(wplus_init_wsock.exe),具体路径为下图 打开浏览器设置代理(Internet选项 ...

  9. What is REST API

    What is REST REST is acronym for REpresentational State Transfer. It is architectural style for dist ...

  10. buffer IO和direct IO

    最近在看很多框架,redis,kafka等底层都涉及到文件IO操作的效率问题,所以查了些资料,看到这篇文章讲的比较明白些,贴出来留存. 链接地址: https://www.ibm.com/develo ...