Java泛型的反射

1. 类型擦除

了解编译器背着我们做了什么很重要。Java中的泛型,在编译后会被擦除类型参数。

如果用instanceof来查询对象的类型,只能查到对应的原始类型(raw type)。

    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泛型的总结

虚拟机中没有泛型,只有普通的类和方法

所以的类型参数都用它们的限定类型替换

桥方法被合成用来保持多态

为保证类型安全性,必要时插入强制类型转换

题外话:编译器在包装类型和基本类型的转换中也会插手,自动装箱、自动拆箱指的就是编译器会插入强制类型转换的代码。注意这是编译期做的,不是运行期。

2. 对泛型的反射

Java中的泛型和继承结合起来,就会涉及到协变、逆变的问题。同样的,由于类型擦除,在用反射处理泛型类型时,处理方法又有区别。JDK1.5为此提供了一个新的接口Type。

虽然有类型擦除,但也不是所有的地方都会被擦除,具体参见R大的这篇博客http://rednaxelafx.iteye.com/blog/586212

下面通过代码说明在使用反射获取泛型的绑定类型时的一种场景,以及如何在该场景下获取绑定的具体类型。

在继续下面的内容前,应该对Java中的ParameterizedType、TypeVariable、Class这些类型有基本的了解。

Java泛型的反射_第1张图片

首先定义一个泛型类,以及一个继承了泛型类的具体类。

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类。


你可能感兴趣的:(Java笔记)