了解编译器背着我们做了什么很重要。Java中的泛型,在编译后会被擦除类型参数。
public static void main(String[] args) { List<Integer> integerList = new ArrayList<>(); List<String> stringList = new ArrayList<>(); Class c1 = integerList.getClass(); Class c2 = stringList.getClass(); System.out.println(c1 == c2); // true System.out.println(c1); // class java.util.ArrayList System.out.println(c2); // class java.util.ArrayList System.out.println(integerList instanceof ArrayList); // true }
注意,像下面这样写会报错:
integerList instanceof ArrayList<Integer>
也就是说,我们用 instanceof 来判断一个对象的类型的时候,只能到raw type这个层面,更进一步的类型参数是无法判断的。
也正因为编译器会擦除类型信息,如下的方法重载会在编译期报错,因为类型擦除后,这两个方法的参数列表是一样的。
void method(List<String> list){} void method(List<Integer> list){}
下面摘一段《Core Java》对Java泛型的总结
虚拟机中没有泛型,只有普通的类和方法
所以的类型参数都用它们的限定类型替换
桥方法被合成用来保持多态
为保证类型安全性,必要时插入强制类型转换
题外话:编译器在包装类型和基本类型的转换中也会插手,自动装箱、自动拆箱指的就是编译器会插入强制类型转换的代码。注意这是编译期做的,不是运行期。
Java中的泛型和继承结合起来,就会涉及到协变、逆变的问题。同样的,由于类型擦除,在用反射处理泛型类型时,处理方法又有区别。JDK1.5为此提供了一个新的接口Type。
虽然有类型擦除,但也不是所有的地方都会被擦除,具体参见R大的这篇博客http://rednaxelafx.iteye.com/blog/586212
下面通过代码说明在使用反射获取泛型的绑定类型时的一种场景,以及如何在该场景下获取绑定的具体类型。
在继续下面的内容前,应该对Java中的ParameterizedType、TypeVariable、Class这些类型有基本的了解。
首先定义一个泛型类,以及一个继承了泛型类的具体类。
public class User<T> { private String name; private int age; private T data; } class VipUser extends User<String> {}
需要注意的是,这里User类是一个泛型,但是VipUser继承的时候,将类型参数绑定到了String类型。
我们先用反射来处理User类。对于非泛型字段,一切正常;但是对于泛型字段,我们只能拿到类型Object。
Class> c = User.class; // 非泛型字段 name Field nameField = c.getDeclaredField("name"); // false System.out.println(nameField.getGenericType() instanceof ParameterizedType); // false System.out.println(nameField.getGenericType() instanceof TypeVariable); // true System.out.println(nameField.getGenericType() instanceof Class); // class java.lang.String System.out.println(nameField.getGenericType()); // 泛型字段 data Field dataField = c.getDeclaredField("data"); // true System.out.println(dataField.getGenericType() instanceof TypeVariable); // T System.out.println(dataField.getGenericType()); // class java.lang.Object System.out.println(dataField.getType());
接下来我们看看对VipUser类的处理:
Class> c = VipUser.class; Class> parent = c.getSuperclass(); Field dataField = parent.getDeclaredField("data"); // class java.lang.Object (getType方法只能拿到Object,拿不到具体类型) System.out.println(dataField.getType()); // T (仍然拿不到具体类型) System.out.println(dataField.getGenericType()); // true System.out.println(dataField.getGenericType() instanceof TypeVariable); // 通过MyBatis的TypeParameterResolver类来获得字段类型 // class java.lang.String System.out.println(TypeParameterResolver.resolveFieldType(dataField, c));
不管是通过getGenericType方法还是getType方法,我们都拿不到定义VipUser类时绑定的String类型,但是通过MyBatis的TypeParameterResolver类,我们得到了data字段的正确类型-String。有兴趣的可以参考一下MyBatis的源码,看看是怎么做的。
下面的代码参考MyBatis的TypeParameterResolver类的源码,针对上面的例子,说明如何拿到泛型绑定的具体类型。
Class> c = VipUser.class; // 注意是调用getGenericSuperclass方法 ParameterizedType parentAsType = (ParameterizedType) c.getGenericSuperclass(); Class> parentAsClass = (Class>) parentAsType.getRawType(); Field dataField = parentAsClass.getDeclaredField("data"); // T Type genericType = dataField.getGenericType(); // 获取泛型的类型参数 TypeVariable[] typeVar = parentAsClass.getTypeParameters(); for (int i = 0; i < typeVar.length; i++) { if (genericType.equals(typeVar[i])) { // class java.lang.String System.out.println(parentAsType.getActualTypeArguments()[i]); break; } }
关于反射和泛型碰到一起时还有很多场景,以上给出的只是其中一种。具体的可以参考MyBatis的TypeParameterResolver类。