阅读更多

1顶
0踩

编程语言
前言
现在很多程序员都会在简历中写上精通 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 引入泛型擦除的原因,也自然能理解如何在运行时获取泛型信息了。
来自: oschina
1
0
评论 共 1 条 请登录后发表评论
1 楼 xurping 2017-04-20 08:50
typeArgument.getTypeName()

java.lang.reflect.Type没有getTypeName()这个方法? 还是你用了更新的JDK版本?

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • Java 泛型总结(一):基本用法与类型擦除

    本文主要介绍了Java泛型的使用以及类型擦除相关的问题。具有很好的参考价值。下面跟着小编一起来看下吧

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

    Java 的泛型擦除和运行时泛型信息获取 Java 的泛型擦除 擦除 Class c1 = new ArrayList&lt;Integer&gt;().getClass(); Class c2 = new ArrayList&lt;String&gt;().getClass(); System.out.println(c1 == c2); /...

  • 【Java】 泛型擦除

    1. 泛型擦除的介绍 ...2. 通过反射获取被擦除的泛型信息 3. 使用匿名内部类解决泛型擦除导致的问题 4. 泛型擦除在Flink中的坑 4.1 算子实参有时用Lambda表达式代替匿名内部类会报错 4.2 new OutputTag(){}

  • java泛型擦除,如何获取泛型信息(retrofit)

    Java的泛型被称作是伪泛型,仅于编译时类型检查,在运行时擦除类型信息. 之所以这样做,是为了向下兼容. 3.泛型擦除后retrofit是怎么获取类型的? 先看下retrofit的代码片段 static &lt;T&gt; ServiceMethod&lt;T&...

  • Java 泛型,你了解类型擦除吗?

    泛型是 Java 中一个很小巧的概念,但同时也是一个很容易让人迷惑的知识点,它让人迷惑的地方在于它的许多表现有点违反直觉。文章开始的地方,先给大家奉上一道经典的测试题。List&amp;lt;String&amp;gt; l1 = new ...

  • java 泛型 捕获转换_[转] Java 的泛型擦除和运行时泛型信息获取

    我觉得不仅要熟练掌握 Java 语法和 JDK 的使用,还需要对 Java 这门语言的各方面原理有深入的了解。除了像并发、JVM 等方面,以及软引用、弱引用等高级知识以外,其实很多我们每天接触到的 Java 特性里面也是另...

  • 泛型(泛型擦除)【java基础----泛型】

    泛型(泛型擦除)【java基础----泛型】

  • Java编程思想之【泛型擦除】

    泛型二、泛型擦除1.擦除的神秘之处迁移兼容性擦除的问题擦除补偿 前言 还记得几年去一间公司面试的时候,面试官问的技术方面的问题,其中一个就是关于擦除的问题,当时的我第一次接触面试有点紧张而且对擦除这个...

  • Java泛型与类型擦除

    “编译器会进行泛型擦除”是一个常识了(好吧,实际擦除的是参数和自变量的类型)。这个过程由“类型擦除”实现。但是并非像许多开发者认为的那样,在 &lt;..&gt; 符号内的东西都被擦除了。看下面这段代码: public ...

  • Java 泛型擦除的理解及如何获取泛型的实际类型

    Java 泛型擦除泛型java 字节码层面Java中获取泛型的类型信息实例化子类中获取泛型未实例化子类无法获取泛型List测试总结 泛型 泛型是JDK1.5之后的一项新增特性, 它的本质是参数化类型(Parametersized Type)的应用...

  • java泛型探秘(二):泛型擦除

    目录 一.泛型擦除是什么 二.... 三....1. 特殊的rawType ...java泛型是编译期的泛型,不是运行时的泛型 java语言是跨平台的,每个平台都有对应的JVM(java虚拟机),编写的java源码不能直接在JVM中运行,能在...

  • JAVA泛型的继承和实现、擦除原理解析

    主要介绍了JAVA泛型的继承和实现、擦除原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

  • 【四二学堂】Java 的泛型擦除和运行时泛型信息获取

    前言 ...我觉得不仅要熟练掌握 Java 语法和 JDK 的使用,还需要对 Java 这门语言的各方面原理有深入的了解。除了像并发、JVM 等方面,以及软引用、弱引用等高级知识以外,其实很多我们每天接触...

  • Java 泛型:泛型和类型擦除详解-修改中...

    Java 泛型的实现是在编译层,编译后生成的字节码中不包含泛型中的类型信息。所以使用泛型时,加上的类型参数,会在编译器编译的时候去掉,这个过程称为类型擦除。System.out.println("两者相同吗?System.out....

  • 才学了泛型,简单说一下我对java泛型擦除和补偿的一些理解

    其实Java 的泛型是伪泛型,在JVM运行的时候,所有的泛型信息都会被擦除掉。举个例子,ArrayList&lt;Integer&gt; list,在编译的时候你调用add()方法,只能传入Integer类型的变量,传入其他类型编译器无法通过 ...

  • 聊聊Java泛型擦除那些事

    文章目录概述泛型泛型信息运行时擦除泛型信息声明时保留结论获取并使用泛型使用场景总结 概述 曾几何时你是否在简历中写道:精通Java、精通xx、精通oo 等等的字眼,说出来不怕丢人,本人也干过。现在想想真是惊叹于...

  • Java泛型擦除和转换

    一、泛型擦除 在编译期间,所有泛型信息都会被擦除掉,在生成的字节码中是不包括泛型中的类型信息的 直接举个例子 public class Main2&amp;lt;T&amp;gt; { public static void main(String[] args) { List&amp;lt...

  • Java泛型和类型擦除

    public class 类名 {// 注意:静态方法和静态变量不能使用泛型类所声明的泛型类型}}}}}public interface 接口名 {}}}@Override}}@Override}}修饰符 返回类型 方法名(参数) {}return t;}

  • 多图表实现员工满意度调查数据分析python

    员工满意度是指员工对于工作环境、待遇、职业发展和组织管理等方面的满意程度。它是衡量员工对工作的整体感受和情绪状态的重要指标。

Global site tag (gtag.js) - Google Analytics