在JDK5引入了泛型
特性以后,她迅速地成为Java编程中不可或缺的元素。然而,就跟泛型乍一看彷佛很是容易同样,许多开发者也很是容易就迷失在这项特性里。
多数Java开发者都会注意到Java编译器
的类型擦除
实现方式,Type Erasure
会致使关于某个Class的全部泛型信息都会在源代码编译时消失掉。在一个Java应用中,能够认为全部的泛型实现类,都共享同一个基础类(注意与继承
区分开来)。这是为了兼容JDK5以前的全部JDK版本,就是人们常常说的向后兼容性
。java
译者注:原文较为琐碎,大体意思是。在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运行时
来讲,上述代码等同于app
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区域的这块泛型信息呢?this
Class.getGenericSuperClass()
方法,来取出一个Type类型的实例
。若是直接父类的实际类型就是泛型类型的话,那取出的Type类型实例
就能够被显示地转换为ParameterizeType
。spa
(Type只是一个标记型接口,它里面仅包含一个方法:getTypeName()
。因此取出的实例的实际类型会是ParameterizedTypeImpl
,但不该直接暴露实际类型,应一直暴露Type接口
)。
ParameterizedType
接口,如今咱们能够直接调用ParameterizeType.getActualTypeArguments()
取出又一个Type类型实例数组
。当数组中的类型参数为非泛型类型时,咱们就能够简单地把它显示转换为Class<?>
。rest
为了保持文章的简洁性,咱们跳过了
GenericArrayType
的状况。
如今咱们可使用以上知识编写一个工具类了:code
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]); } }