Java 的泛型擦除和运行时泛型信息获取
2017-04-24 10:14
204 查看
Java 的泛型擦除
代码一
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基础加强--14.利用反射操作泛型III【解析关于泛型类型的细节信息的获取方法】【Method与泛型相关的方法】【个人总结】
- Java使用反射来获取Map的泛型信息
- java获取当前应用的运行信息(内存,线程,运行时间,状态等)
- 深入理解 Java 泛型:类型擦除、通配符、运行时参数类型获取
- Java使用反射来获取成员变量泛型信息
- Java高级--Java线程运行栈信息的获取 getStackTrace()
- Java获取 JVM 运行信息
- Java 运行时如何获取泛型参数的类型
- JAVA 获取当前执行的函数名、当前运行的类名等等信息
- Java 运行时如何获取泛型参数的类型
- Java 反射(2):泛型相关周边信息获取
- 获取java程序运行时内存信息
- 运行时获取方法调用堆栈信息(java)
- 运行时获取方法调用堆栈信息(java)
- Java 反射:Class类,动态加载类获取方法和成员变量构造信息,方法反射的基本操作,集合泛型的本质
- 夯实JAVA基本之二 —— 反射(2):泛型相关周边信息获取
- Java 类型信息 —— 获取泛型类型的类对象(.class)