前篇的文章中也提到了,泛型是帮助开发者避免程序运行时出现异常转换类型错误而设计的,使用在开发阶段。编译时编译器会进行泛型擦除,运行期通过反射获取不到运行时的实际泛型类型信息。
那么,这句话到底对还是不对呢?什么情况下可以反射到泛型的类型信息,什么情况下不能呢?
首先,明确一个问题。
泛型中的标记符(T、E、K、V等)与泛型的通配符(?)有什么区别的?
泛型的标记符出现在泛型类或泛型方法的定义中。
泛型的通配符出现在泛型类或泛型方法的使用中。
比如,要定义一个MyArrayList.class。这个类要具备泛型的功能,于是MyArrayList<T>.class就定义了泛型。
比如,在某个方法或属性或子类中要使用MyArrayList<T>.class这个类,就可以使用通配符了 private list = new MyArrayList<?>();
那么List<? extends T>用于什么地方呢?当然是使用在泛型的定义中。
接下来,回到正题。定义一个Box<T>:
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
在使用的代码中尝试通过反射get()
获取方法返回值的泛型类型信息:
public void test() {
Box<Integer> box = new Box<>();
Method method = box.getClass().getMethod("get", null);
Type genericReturnType = method.getGenericReturnType();
System.out.println(genericReturnType instanceof ParameterizedType);
if (genericReturnType instanceof ParameterizedType){
System.out.println(genericReturnType.getTypeName());
}
}
运行代码,控制台输出 "false" ,这明显不是我们想要得到的 "java.lang.Integer"。
修改Box<T>的代码为:
public class Box<T extends Number> {
}
再次运行代码,控制台依然输出 "false" 。
这两次的改动说明编译器进行了泛型擦除,反射得不到泛型的类型信息。
那么,什么情况下反射能得到泛型的类型信息呢?
修改Box<T>的代码:
public class MyBox extends Box<Number>{}
在使用的代码中尝试通过反射MyBox.class得到父类Box.class泛型类型信息<Number>:
public void test() throws Exception {
Type genericSuperclass = MyBox.class.getGenericSuperclass();
System.out.println(genericSuperclass instanceof ParameterizedType);
if (genericSuperclass instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments();
for (Type type : actualTypeArguments) {
System.out.println(type.getTypeName());
}
}
}
运行代码,控制台输出"true"和"java.lang.Number"。
说明成功获取到了父类Box.class泛型类型信息<Number>。
如果反射MyBox.class得到从父类Box继承来的的方法get(),能不能获取到泛型类型信息呢?
读者可以自行尝试一下,答案是否定的。
回到本文的入题,什么情况下可以反射到泛型的类型信息,什么情况下不能呢?
个人理解:在编译器已经明确了泛型的具体类型信息,或者使用通配符明确了泛型类型的上限活者下限的具体类型类型信息,则是可以通过反射获取到该泛型的具体类型信息的。
换句话说:在定义泛型的类中是不能反射得到具体的泛型类型信息的,在使用泛型的类中是可以的。
(先看明白本文开篇所提到的定义泛型和使用泛型的概念)
在具体的使用上,直接上代码:
public class MyBox extends Box<Number> { // 父类有具体的泛型信息
public List<String> list; // 属性有具体的泛型信息
public List<Long> get(List<Integer> list){ // 方法的参数有具体的泛型信息,方法的返回值有具体的泛型信息
return new ArrayList<Long>();
}
public static void main(String[] args) throws Exception {
MyBox myBox = new MyBox();
myBox.testReflectSuperClass(); // 获取父类的泛型信息
myBox.testReflectField(); // 获取属性的泛型信息
myBox.testReflectMethodReturnType(); // 获取方法返回值的泛型信息
myBox.testReflectMethodParameterType(); // 获取方法参数的泛型信息
}
public void testReflectSuperClass(){
Type genericSuperclass = MyBox.class.getGenericSuperclass();
test(genericSuperclass);
}
public void testReflectField() throws Exception {
Type genericType = MyBox.class.getField("list").getGenericType();
test(genericType);
}
public void testReflectMethodReturnType() throws Exception {
Type genericReturnType = MyBox.class.getMethod("get", List.class).getGenericReturnType();
test(genericReturnType);
}
public void testReflectMethodParameterType() throws Exception {
Type[] genericParameterTypes = MyBox.class.getMethod("get", List.class).getGenericParameterTypes();
for (Type type : genericParameterTypes) {
test(type);
}
}
public void test(Type genericType){
if (genericType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
for (Type type : actualTypeArguments) {
System.out.println(type.getTypeName());
}
}
}
}
运行代码,控制分别输出
java.lang.Number
java.lang.String
java.lang.Long
java.lang.Integer
说明通过反射获取到了运行期的泛型的具体的类型信息。
总结:
1,在定义泛型的类中使用的是标记符,编译后的class中没有泛型信息;
2,在定义泛型的类中使用了<? extends T>,属于未标明具体类型信息,编译后的class中没有泛型信息;
3,在使用泛型的类中指定了具体的泛型信息<String>,编译后的class中包含泛型信息<String>;
4,在使用泛型的类中指定了具体的泛型信息<? extends Number>,编译后的class中包含泛型信息<? extends Number>;
5,可以获取泛型的具体类型信息包括:
- 父类或父接口的泛型信息;
- 属性的泛型信息;
- 方法参数的泛型信息;
- 方法返回值的泛型信息。
备注:
1,Type是Class、Field、Method等的父接口。如果判断Type属于Class类型,则可以使用Class clazz = (Class)Type; 通过强转来获取泛型的具体实现类的class对象。
2,如果使用了<? extends Number>通配符来使用泛型信息,则反射得到的Type的实现类为sun.reflect.generics.reflectiveObjects.WildcardTypeImpl,该类的直接父接口为java.lang.reflect.WildcardType。该接口的方法有:Type[] getUpperBounds();和Type[] getLowerBounds();来获取上下边界。代码实例:
public class MyBox extends Box<Number> {
public List<String> list;
public List<Long> get(List<? extends Integer> list){
return new ArrayList<Long>();
}
public static void main(String[] args) throws Exception {
Type[] genericParameterTypes = MyBox.class.getMethod("get", List.class).getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
if (genericParameterType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type type : actualTypeArguments) {
if (type instanceof WildcardType) {
Type[] upperBounds = ((WildcardType) type).getUpperBounds();
for (Type upperBoundType : upperBounds) {
System.out.println(upperBoundType.getTypeName());
}
}
}
}
}
}
}