Java泛型进阶 - 如何取出泛型类型参数
在JDK5引入了泛型
特性之后,她迅速地成为Java编程中不可或缺的元素。然而,就跟泛型乍一看似乎非常容易一样,许多开发者也非常容易就迷失在这项特性里。
多数Java开发者都会注意到Java编译器
的类型擦除
实现方式,Type Erasure
会导致关于某个Class的所有泛型信息都会在源代码编译时消失掉。在一个Java应用中,可以认为所有的泛型实现类,都共享同一个基础类(注意与继承
区分开来)。这是为了兼容JDK5之前的所有JDK版本,就是人们经常说的向后兼容性
。
向后兼容性
译者注:原文较为琐碎,大致意思是。在JVM整个内存空间中,只会存在一个
ArrayList.class
。
为了能够区分ArrayList<String>
和ArrayList<Integer>
,现在假想的实现方式是在Class文件信息表(函数表+字段表)
里添加额外的泛型信息。这个时候JVM的内存空间中就会存在(假设)ArrayList&String.class
和(假设)ArrayList&Integer.class
文件。顺着这种情况延续下去的话,就必须要修改JDK5之前所有版本的JVM对Class文件
的识别逻辑,因为它破坏了JVM内部一个Class只对应唯一一个.class
这条规则。这也是人们常说的: 破坏了向后兼容性
。注:参考Python3舍弃掉Python2的例子,也是放弃了对2的兼容,Python3才能发展并构造更多的新特性。
那应该怎么做?
既然Java开发团队选择了兼容JDK5之前的版本,那就不能在JVM
里做手脚了。但Java编译器
的代码似乎还是可以修改的。于是,Java编译器
在编译时
就会把泛型信息都擦除,所以以下的比较在JVM运行时
会永远为真。
assert new ArrayList<String>().getClass() == new ArrayList<Integer>().getClass();
对JVM运行时
来说,上述代码等同于
assert new ArrayList.class == ArrayList.class
到目前为止,上述内容都是大家所熟知的事情。然而,与普遍印象相反的是,某些情况下在运行时
获取到泛型类型信息也是可行的。举个栗子:
class MyGenericClass<T> { }
class MyStringSubClass extends MyGenericClass<String> { }
MyStringSubClass
相当于对MyGenericClass<T>
做了类型参数赋值T = String
。于是,Java编译器
可以把这部分泛型信息(父类MyGenericClass的泛型参数是String),存储在它的子类MyStringSubClass的字节码区域中。
而且因为这部分泛型信息在被编译后,仅仅被存储在被老版JVM所忽略的字节码区域中,所以这种方式并没有破坏向后兼容性
。与此同时,因为T已经被赋值为String,所有的MyStringSubClass类的对象实例仍然共享同一个MyStringSubClass.class
。
如何获取这块泛型信息?
应该如何获取到被存储在byte code区域的这块泛型信息呢?
- Java API提供了
Class.getGenericSuperClass()
方法,来取出一个Type类型的实例
。 如果直接父类的实际类型就是泛型类型的话,那取出的
Type类型实例
就可以被显示地转换为ParameterizeType
。(Type只是一个标记型接口,它里面仅包含一个方法:
getTypeName()
。所以取出的实例的实际类型会是ParameterizedTypeImpl
,但不应直接暴露实际类型,应一直暴露Type接口
)。- 感谢
ParameterizedType
接口,现在我们可以直接调用ParameterizeType.getActualTypeArguments()
取出又一个Type类型实例数组
。 - 父类所有的泛型类型参数都会被包含在这个数组里,并且以被声明的顺序放在数组对应的下标中。
当数组中的类型参数为非泛型类型时,我们就可以简单地把它显示转换为
Class<?>
。为了保持文章的简洁性,我们跳过了
GenericArrayType
的情况。
现在我们可以使用以上知识编写一个工具类了:
public static Class<?> findSuperClassParameterType(Object instance, Class<?> clazzOfInterest, int parameterIndex) {Class<?> subClass = instance.getClass();while (subClass.getSuperclass() != clazzOfInterest) {subClass = subClass.getSuperclass();if (subClass == null) throw new IllegalArgumentException();}ParameterizedType pt = (ParameterizedType) (subClass.getGenericSuperclass());return (Class<?>) pt.getActualTypeArguments()[parameterIndex];
}public static void testCase1() {Class<?> genericType = findDirectSuperClassParameterType(new MyStringSubClass());System.out.println(genericType);assert genericType == String.class;
}
然而,请注意到
findSuperClassParamerterType(new MyGenericClass<String>(), MyGenericClass.class, 0)
这样调用会抛出IllegalArgumentException
异常。之前说过:泛型信息
只有在子类的帮助下才能被取出。然而,MyGenericClass<String>
只是一个拥有泛型参数的类,并不是MyGenericClass.class
的子类。没有显式的子类,就没有地方存储String类型参数
。因此上述调用不可避免地会被Java编译器
进行类型擦除
。如果你已预见到你的项目中会出现这种情况,也想要避免它,一种良好的编程实践是将MyGenericClass
声明为abstract
。
然而,我们还没有解决问题,毕竟我们目前为止还有许多坑没有填。
链式泛型
class MyGenericClass<T> {}
class MyGenericSubClass<U> extends MyGenericClass<U> {}
class MyStringSubSubClass extends MyGenericSubClass<String> {}
如下调用,仍然会抛出异常。
findSuperClassParameterType(new MyStringSubClass(), MyGenericClass.class, 0);
这又是为什么呢?到目前为止我们都在设想:MyGenericClass
的类型参数T
的相关信息会存储在它的直接子类
中。那么上述的类继承关系就有以下逻辑:
MyStringSubClass.class
中存储了MyGenericSubClass<U> --> U = String
。MyGenericSubClass.class
中仅存储了MyGenericClass<T> --> T = U
但U
并不是一个Class类型
,而是TypeVariable类型
的类型变量,如果我们想要解析这种继承关系,就必须解析它们之间所有的依赖关系。代码如下:
public static Class<?> findSubClassParameterType(Object instance, Class<?> classOfInterest, int parameterIndex) {Map<Type, Type> typeMap = new HashMap<>();Class<?> instanceClass = instance.getClass();while (instanceClass.getSuperclass() != classOfInterest) {extractTypeArguments(typeMap, instanceClass);instanceClass = instanceClass.getSuperclass();if (instanceClass == null) throw new IllegalArgumentException();}// System.out.println(typeMap);ParameterizedType pt = (ParameterizedType) instanceClass.getGenericSuperclass();Type actualType = pt.getActualTypeArguments()[parameterIndex];if (typeMap.containsKey(actualType)) {actualType = typeMap.get(actualType);}if (actualType instanceof Class) {return (Class<?>) actualType;} else {throw new IllegalArgumentException();}
}private static void extractTypeArguments(Map<Type, Type> typeMap, Class<?> clazz) {Type genericSuperclass = clazz.getGenericSuperclass();if (!(genericSuperclass instanceof ParameterizedType)) {return ;}ParameterizedType pt = (ParameterizedType) genericSuperclass;Type[] typeParameters = ((Class<?>) pt.getRawType()).getTypeParameters();Type[] actualTypeArguments = pt.getActualTypeArguments();for (int i = 0; i < typeParameters.length; i++) {if (typeMap.containsKey(actualTypeArguments[i])) {actualTypeArguments[i] = typeMap.get(actualTypeArguments[i]);}typeMap.put(typeParameters[i], actualTypeArguments[i]);}
}
代码中通过一个map可以解析所有链式泛型类型
的定义。不过仍然不够完美,毕竟MyClass<A, B> extends MyOtherClass<B, A>
也是一种完全合法的子类定义。
嵌套类
好了好了,仍然没有结束:
class MyGenericOuterClass<U> {public class MyGenericInnerClass<U> { }
}
class MyStringOuterSubClass extends MyGenericOuterClass<String> { }MyStringOuterSubClass.MyGenericInnerClass inner = new MyStringOuterSubClass().new MyGenericInnerClass();
下面这样调用仍然会失败。
findSuperClassParameterType(inner, MyGenericInnerClass.class, 0);
这种失败几乎是可预见的,我们正试图在MyGenericInnerClass的对象实例
里面寻找MyGenericInnerClass
的泛型信息。就像之前所说,因为MyGenericInnerClass
并没有子类,所以从MyGenericInnerClass.class
中寻找泛型信息是不可能的,毕竟MyGenericInnerClass.class
里面根本就不存在泛型信息
。不过在这个例子中,我们检查的是MyStringOuterSubClass
中的非static内部类: MyGenericInnerClass
的对象实例。那么,MyStringOuterSubClass
是知道它的父类MyGennericOuterClass<U> --> U = String
。当使用反射取出MyGenericInnerClass
中的类型参数时,就必须把这点纳入考量。
现在这件事就变得相当棘手了。
-> 为了取出MyGenericOuterClass
的泛型信息
-> 就必须先得到MyGenericOuterClass.class
这依然可以通过反射取得,Java编译器
会在内部类MyGenericInnerClass
中生成一个synthetic-field: this$0
,这个字段可以通过Class.getDeclaredField("this$0")
获取到。
> javap -p -v MyGenericOuterClass$MyGenericInnerClass.class
...
...final cn.local.test.MyGenericOuterClass this$0;descriptor: Lcn/local/test/MyGenericOuterClass;flags: ACC_FINAL, ACC_SYNTHETIC
...
既然已经有办法可以获取到MyGenericOuterClass.class
了,那接下来我们似乎可以直接复用之前的扫描逻辑了。
这里需要注意,
MyGenericOuterClass<U>的U 并不等同于 <MyGenericInnerClass<U>的U
。
我们可以做以下推理,MyGenericInnerClass
是可以声明为static
的,这就意味着static
情况下,MyGenericInnerClass
拥有它自己独享的泛型type命名空间
。所以,Java API中所有的TypeVariable接口实现类
,都拥有一个属性叫genericDeclaration
。
如果两个泛型变量
被分别定义在不同的类中,那么这两个TypeVariable类型
变量,从genericDeclaration
的定义上来说就是不相等的。
获取嵌套类的泛型的代码如下:
private static Class<?> browseNestedTypes(Object instance, TypeVariable<?> actualType) {Class<?> instanceClass = instance.getClass();List<Class<?>> nestedOuterTypes = new LinkedList<Class<?>>();for (Class<?> enclosingClass = instanceClass.getEnclosingClass();enclosingClass != null;enclosingClass = enclosingClass.getEnclosingClass() ) {try {Field this$0 = instanceClass.getDeclaredField("this$0");Object outerInstance = this$0.get(instance);Class<?> outerClass = outerInstance.getClass();nestedOuterTypes.add(outerClass);Map<Type, Type> outerTypeMap = new HashMap<>();extractTypeArguments(outerTypeMap, outerClass);for (Map.Entry<Type, Type> entry : outerTypeMap.entrySet()) {if (!(entry.getKey() instanceof TypeVariable)) {continue;}TypeVariable<?> foundType = (TypeVariable<?>) entry.getKey();if (foundType.getName().equals(actualType.getName())&& isInnerClass(foundType.getGenericDeclaration(), actualType.getGenericDeclaration())) {if (entry.getValue() instanceof Class) {return (Class<?>) entry.getValue();}actualType = (TypeVariable<?>) entry.getValue();}}} catch (NoSuchFieldException e) {/* however, this should never happen. */} catch (IllegalAccessException e) {/* this might happen */}}throw new IllegalArgumentException();
}private static boolean isInnerClass(GenericDeclaration outerDeclaration, GenericDeclaration innerDeclaration) {if (!(outerDeclaration instanceof Class) || !(innerDeclaration instanceof Class)) {throw new IllegalArgumentException();}Class<?> outerClass = (Class<?>) outerDeclaration;Class<?> innerClass = (Class<?>) innerDeclaration;while ((innerClass = innerClass.getEnclosingClass()) != null) {if (innerClass == outerClass) {return true;}}return false;
}private static void extractTypeArguments(Map<Type, Type> typeMap, Class<?> clazz) {Type genericSuperclass = clazz.getGenericSuperclass();if (!(genericSuperclass instanceof ParameterizedType)) {return;}ParameterizedType pt = (ParameterizedType) genericSuperclass;Type[] typeParameters = ((Class<?>) pt.getRawType()).getTypeParameters();Type[] actualTypeArguments = pt.getActualTypeArguments();for (int i = 0; i < typeParameters.length; i++) {if (typeMap.containsKey(actualTypeArguments[i])) {actualTypeArguments[i] = typeMap.get(actualTypeArguments[i]);}typeMap.put(typeParameters[i], actualTypeArguments[i]);}
}
Java泛型进阶 - 如何取出泛型类型参数相关推荐
- 0.Java进阶学习之泛型
1. 什么是泛型? 泛型是Java SE 1.5的新特性. "泛型"意味着编写的代码可以被不同类型的对象所重用. 泛型的本质是参数化类型. 也就是说,泛型是将所操作的数据类型作为参 ...
- Java泛型进阶篇: 无界通配符、上界通配符以及下界通配符
专栏文章导航 Java泛型入门篇: 泛型类.泛型接口以及泛型方法 Java泛型进阶篇: 无界通配符.上界通配符以及下界通配符 Java泛型原理篇: 类型擦除以及桥接方法 文章目录 前言 1. 无界通配 ...
- Java面向对象系列[v1.0.0][泛型进阶]
所谓泛型,就是允许在定义类.接口.方法时使用类型形参,这个类型形参(或者叫泛型)将在声明变量.创建对象.调用方法时动态地指定(即传入实际的类型参数也可称为类型实参) Java5改写了集合框架中的全部接 ...
- Java:高级之泛型概念引入,泛型可以设置多个类型参数,泛型继承和泛型接口实现,限制泛型可用类型,泛型通配的方式,泛型方法,泛型方法限制泛型可用类型
目录页 泛型简介 泛型类 限制泛型可用类型 类型通配声明 泛型方法 问题引入 如果我们需要产生多个对象,每个对象的逻辑完 ...
- java 接口的泛型方法_Java泛型/泛型方法/通配符/泛型接口/泛型泛型擦出
从JDK1.5以后引入了三大常用新特性:泛型.枚举(enum).注解(Annotation).其中JDK1.5中泛型是一件非常重要的实现技术,它可以帮助我们解决程序的参数转换问题.本文为大家详细介绍一 ...
- Java学习之什么是泛型的擦除
序 上一章讲了泛型的基本使用方式,但是在上一章中提到了擦除这一概念. 我们都知道Java一开始并没有泛型这一语言特性,泛型这一特性是在JavaSE5出现的,也就是说,在该版本以前,所有的库都是以原有的 ...
- java泛型常用特点_Java泛型详解
对java的泛型特性的了解仅限于表面的浅浅一层,直到在学习设计模式时发现有不了解的用法,才想起详细的记录一下. 本文参考java 泛型详解.Java中的泛型方法. java泛型详解 1. 概述 泛型在 ...
- java 泛型 t extends_Java 之泛型通配符 ? extends T 与 ? super T 解惑
简述 大家在平时的工作学习中, 肯定会见过不少如下的语句: List super T> List extends T> 我们都知道, 上面的代码时关于 Java 泛型的, 那么这两个不同的 ...
- Java编程的逻辑 (36) - 泛型 (中) - 解析通配符
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
最新文章
- iphone开发 NSURL中fileURLWithPath和URLWithString的区别
- 解决spring-boot-maven-plugin not found爆红
- boost::multiprecision模块cpp_dec_float_100相关的测试程序
- Tarjan求lca
- 从zip中读取文件 合并到指定的文件中
- oracle查询报错clb,Oracle RAC 负载均衡与故障转移(三)
- 前端:CSS/10/伪类选择器,CSS列表属性,CSS边框属性,CSS内边距属性,CSS背景属性
- Python+psutil获取本机所有联网的应用程序信息
- 重装系统后遇到Git Authentication failed 错误
- 毕业生推荐表计算机水平新国二,实用信息 | 2018届毕业指南:毕业生推荐表与三方协议...
- We7从这里开始---认识we7
- USBCNC输出板与VFD和主轴的使用
- 用百用计算机弹出,CPU使用率老是100%怎么办?小编详解解决CPU过高的问题
- Git 配合G码云使用基本操作相关指令
- javascript机器学习
- LIO-SAM后端中的回环检测及位姿计算
- 集合多种功能的转录调控数据库:hTFtarget,介绍及使用教程
- OpenCore电池显示正在充电:1% 建议维修
- 代理,正向代理和反向代理
- idea 启动SpringBoot项目出现java程序包:xxx不存在
热门文章
- TCMalloc:线程缓存的Malloc
- Windows客户端C/C++编程规范“建议”——文件
- 通过OpenSSL的接口实现Base64编解码
- 【linux】串口编程(一)——配置串口
- java 泛型对象实例化_在java中实例化泛型类型
- mfc 两个工具栏合并_(海外观点)MFC,微型电商仓的新玩法?
- lammps软件_MAPS软件:让LAMMPS运用更高效 — 特点及实例
- umi脚手架搭建的项目_15天零成本搭建静态博客,托管于Github Page
- “ Error:(1, 1) java: 非法字符: ‘\ufeff‘ ”错误的解决方法
- oracle9i安装不上,终于成功安装oracle9i了(Cent OS 4.0+oracle9204)