java范型运行时类型获取
反射是我喜欢喜欢 java 的一个重要原因,它在保证静态语言特性的同时,为你提供了一些动态的特性,你可以通过反射做一些很令人惊奇的事情。本文将通过反射和继承来让你获取泛型的运行时类型。
java 泛型使用的是 type erasure。它的泛型代码只有一份,泛型实现需要在编译和运行时都进行一定的操作。
它的反射机制也为我们获取运行时泛型类型提供了一些接口:java.lang.reflect.TypeVariable 和 java.lang.reflect.ParameterizedType。
1. 类型变量的声明
TypeVariable类型变量是各种类型变量的通用超接口,主要保存了类型变量(泛型)的具体声明的信息,比如:类型变量的名称,边界等,但是你无法通过该类来获取具体的运行时类型。
它就像是泛型类或者接口的泛型部分的声明而已。而TypeVariable接口只要和另一个接口java.lang.reflect.GenericDeclaration泛型声明紧密关联。
1.1 GenericDeclaration 可以泛型声明的元素
GenericDeclaration是 Java 反射包中,所有可以声明泛型类型的语法元素的父接口。它只有一个方法:
TypeVariable<?>[] getTypeParameters();
这个方法是懒加载泛型声明的所有的类型变量信息。
该接口有三个实现:java.lang.Class、java.lang.reflect.Constructor和java.lang.reflect.Method,也就是说我们可以在类、构造函数、方法上使用泛型变量。
通过调用对应反射的 #getTypeParameters() 方法就可以得到具体的泛型声明信息。
1.2 TypeVariable 泛型声明
接下来让我们看看泛型声明具体都包含了什么信息:
1 | |
首先该接口是个泛型接口,里面包含了刚刚的 GenericDeclaration ,即它的信息中包含了具体泛型申明的位置:方法、构造函数还是普通函数,这个信息可以通过 #getGenericDeclaration() 方法获取到。
另外 #getBounds() 可以获取泛型的上限边界信息,#getName() 可以获取泛型声明时的名称,比如我们常用的 <T> 这个 T。
2. 运行时类型的获取
关于运行时类型的获取我们首先要知道一个接口:java.lang.reflect.ParameterizedType 所有泛型运行时的类或者接口都会实现这个接口,将具体的参数类型信息保存在实现中。
1 | |
这里我们就主要关注 getActualTypeArguments() 方法,这个方法就是所有的子类具体泛型参数类型的列表,这个长度和 TypeVariable 中的泛型参数声明列表应该是一样大小的。
java 的泛型是通过 type erasure 实现的,所以普通的静态泛型方法是不能获取到泛型类型的,它在运行时使用的是 cast 来帮助方法的执行。
因为在反射机制中提供了关于继承/实现泛型基类/接口的具体细节,这些细节可以通过 Class#getGenericInfo()获取,注意这是在子/实现类中保存的,我们可以通过 Class 类中的两个具体方法来获取具体的运行时类型:Class<?>#getGenericSuperclass() 获取所有的超类类型;Class<?>#getGenericInterfaces() 获取所有的接口类型。这两个方法可以得到当前类的所有直接超类和接口,然后遍历这些类或者接口如果是 ParameterizedType 类型的,就可以将其转换为 ParameterizedType 然后获取具体的泛型参数类型了。
一定要注意,普通的静态方法是不能获取到具体的参数类型的,因为它在字节码阶段全部都被编译成 java.lang.Object 在运行阶段通过 cast 才转换成你想要的具体类型。