我所理解的JDK泛型(二):反射与泛型擦除

前篇的文章中也提到了,泛型是帮助开发者避免程序运行时出现异常转换类型错误而设计的,使用在开发阶段。编译时编译器会进行泛型擦除,运行期经过反射获取不到运行时的实际泛型类型信息。java

  那么,这句话到底对仍是不对呢?什么状况下能够反射到泛型的类型信息,什么状况下不能呢?this

首先,明确一个问题。设计

泛型中的标记符(T、E、K、V等)与泛型的通配符(?)有什么区别的?code

泛型的标记符出如今泛型类或泛型方法的定义中。
泛型的通配符出如今泛型类或泛型方法的使用中。对象

好比,要定义一个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()获取方法返回值的泛型类型信息: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());
                    }
                   }
                }
            }
        }

    }
}
相关文章
相关标签/搜索