原文链接 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. promise之我见

    在我们平时的方法中有很多方法是promise封装的, 有些函数后边跟的then和catch 就是promise的方法,先看一下pormise的特点 (1)对象的状态不受外界影响.Promise对象代表 ...

  2. thinkphp5实现多级控制器

    默认情况下目录结构 application ->admin->controller->class.php 当项目比较多的时候,目录下控制器文件较多,考虑按模块增加一层目录 appli ...

  3. python3下获取全局坐标

    前段时间自己想用python写一个关于截屏翻译的小功能,所以首先需要获取鼠标的坐标动作有按下.拖拽.释放.查找了很多资料无非是pyhook或者用其他语言重写.但是自己用的是python3.65,第三方 ...

  4. API Test WebApiTestClient工具安装及使用

    一.guget安装: 1.解决方案右键-管理解决方案的nuget程序包打开如下图: 搜索WebApiTestClient,然后选择查询出的项目,右边点击安装即可:   2.安装会有如下图提示: 确定即 ...

  5. windows service 2008 R2 升级 sp1遇到的问题

    因为我的程序是以vs2015开发的,所以在在布署windows service 2008 R2 项目的时候报出 红框里的错,说明要安装.net framework4.6. 感觉so easy,下载一个 ...

  6. centos 批量杀死进程

    ps aux | grep 进程名| grep -v grep | awk '{print $2}' | xargs kill -9

  7. prefProvider.kt

    package com.gh0u1l5.wechatmagician.frontend import android.content.ContentProvider import android.co ...

  8. Sum of Subsequence Widths LT891

    Given an array of integers A, consider all non-empty subsequences of A. For any sequence S, let the  ...

  9. 初识spark的MLP模型

    初识Spark的MLP模型 1. MLP介绍 Multi-layer Perceptron(MLP),即多层感知器,是一个前馈式的.具有监督的人工神经网络结构.通过多层感知器可包含多个隐藏层,实现对非 ...

  10. JavaScript中的闭包永远都存储在内存中,除非关闭浏览器

    //閉包實現累加功能 function fn1() { var n = 1; add = function() { n += 1; } function fn2() { n += 1; console ...