在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才能发展并构造更多的新特性。As a consequence既然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<?> classOfInterest, int parameterIndex) { Class<?> subClass = instance.getClass(); while (subClass != subClass.getSuperClass()) { // instance.getClass()不是classOfInterest的子类, // 或者,instance就是classOfInterest的直接实例 subClass = subClass.getSuperClass(); if (subClass == null) throw new IllegalArgumentException(); } ParameterizedType pt = (ParameterizedType) subClass.getGenericSuperClass(); return (Class<?>) pt.getActualTypeArguments()[parameterIndex];}public static void main(String[] args) { Class<?> genericType = findSuperClassParameterType(new MyStringSubClass(), MyGenericClass.class, 0); assert genericType == String.class;}然而,请注意到findSuperClassParamerterType(new MyGenericClass<String>(), MyGenericClass.class, 0)这个方法的实现会抛出异常。像之前说过的:泛型信息仅有在子类的帮助下才能被取出。然而,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会将T映射成String。但凡是总有无赖,现在MyStringSubSubClass将U映射成String,此时MyGenerciSubClass仅仅知道U = T。U甚至都不是一个实际类型,仅仅是Java TypeVariable类型的类型变量,如果我们想要解析这种继承关系,就必须解析它们之间所有的依赖关系。当然还是能做到的:public static Class<?> findSubClassParameterType(Object instance, Class<?> classOfInterest, int parameterIndex) { Map<Type, Type> typeMap = new HashMap<Type, Type>(); Class<?> instanceClass = instance.getClass(); while (classOfInterest != instanceClass.getSuperclass()) { extractTypeArguments(typeMap, instanceClass); instanceClass = instanceClass.getSuperclass(); if (instanceClass == null) throw new IllegalArgumentException(); } ParameterizedType parameterizedType = (ParameterizedType) instanceClass.getGenericSuperclass(); Type actualType = parameterizedType.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 parameterizedType = (ParameterizedType) genericSuperclass; Type[] typeParameter = ((Class<?>) parameterizedType.getRawType()).getTypeParameters(); Type[] actualTypeArgument = parameterizedType.getActualTypeArguments(); for (int i = 0; i < typeParameter.length; i++) { if(typeMap.containsKey(actualTypeArgument[i])) { actualTypeArgument[i] = typeMap.get(actualTypeArgument[i]); } typeMap.put(typeParameter[i], actualTypeArgument[i]); }}