反射中与泛型相关的接口和类
1 Type
java中所有类型的公共实现接口,实现该接口的有原始类型,参数化类型,数组类型,类型变量和基本类型。
2 GenericDeclaration
声明类型变量的所有实体的公共接口
`TypeVariable<?>[] getTypeParameters();`返回泛型声明中的类型变量 如Map<K,V>中返回的是键值K,V
3 ParameterizedType 参数化类型
Type[] getActualTypeArguments();
Type getRawType(); 原始类型 ,如Class<T>的实例类Class<String>的RawType是Class
Type getOwnerType(); 泛型类是内部类
泛型与反射
Java中的泛型是一种伪泛型,会在编译期间被擦出,所以无法在运行时获得有关泛型的信息。
但是在某些情况下,可以在运行期获得泛型的信息。
下面是两个典型的使用泛型的场景:
1 声明一个需要被参数化(parameterizable)的类或者接口
2 使用一个参数化的类
当你声明一个类或者接口的时候你可以指明这个类或接口可以被参数化, java.util.List 接口就是典型的例子。你可以运用泛型机制创建一个标明存储的是String类型List,这样比你创建一个Object的List要更好。
你不能在运行期获知一个被参数化的类型的具体参数类型是什么,但是你可以在用到这个被参数化类型的方法以及变量中找到他们,换句话说就是获知他们具体的参数化类型。
- 泛型方法返回类型
获得了java.lang.reflect.Method对象,那么就可以获取到这个方法的泛型返回类型信息。
如果方法是在一个被参数化类型之中(如 T fun())那么你无法获取他的具体类型,但是如果方法返回一个泛型类(译者注:如 List fun())那么你就可以获得这个泛型类的具体参数化类型。)
class MyClass {
private List<String> stringList = new ArrayList<String>();
public List<String> getStringList() {
return this.stringList;
}
}
我们可以获取 getStringList()方法的泛型返回类型,换句话说,我们可以检测到 getStringList()方法返回的是 List 而不仅仅只是一个 List。
Method method = MyClass.class.getMethod("getStringList",null);
Type returnType = method.getGenericReturnType();
if(returnType instanceof ParameterizedType) {
ParameterizedType type = (ParameterizedType)returnType;
Type[] typeArguments = type.getActualTypeArguments();
for(Type typeArgument : typeArguments) {
Class typeArgClass = (Class) typeArgument;
System.out.println("typeArgClass = " + typeArgClass);
}
}
输出:typeArgClass = class java.lang.String
Class类实现了Type接口,这里通过反射获得了参数类型的信息。
- 泛型方法参数类型
可以通过反射来获取方法参数的泛型类型。
method = MyClass.class.getMethod("setStringList", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for(Type genericParameterType : genericParameterTypes){
if(genericParameterType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericParameterType;
Type[] parameterArgTypes = aType.getActualTypeArguments();
for(Type parameterArgType : parameterArgTypes){
Class parameterArgClass = (Class) parameterArgType;
System.out.println("parameterArgClass = " + parameterArgClass);
}
}
}
输出:parameterArgClass = class java.lang.String
- 泛型变量类型
通过反射可以访问公有(public)变量的泛型类型。无论是一个类的静态成员变量还是实例成员变量,都可以获取到。
Field field = MyClass.class.getField("stringList");
Type genericFieldType = field.getGenericType();
if(genericFieldType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericFieldType;
Type[] fieldArgTypes = aType.getActualTypeArguments();
for(Type fieldArgType : fieldArgTypes){
Class fieldArgClass = (Class) fieldArgType;
System.out.println("fieldArgClass = " + fieldArgClass);
}
}
输出:fieldArgClass = class java.lang.String
补充:在集合中使用参数化类型,这样禁止我们在编译期间插入与类型不符合的参数,但是通过反射,由于反射是在运行期发生作用,且java中的泛型在编译期间就会被擦除了,因此可以跳过编译期间的检查,插入任意类型的数据。
public class GenericEssence {
public static void main(String[] args) {
List list1 = new ArrayList(); // 没有泛型
List<String> list2 = new ArrayList<String>(); // 有泛型
/*
* 1.首先观察正常添加元素方式,在编译器检查泛型,
* 这个时候如果list2添加int类型会报错
*/
list2.add("hello");
// list2.add(20); // 报错!list2有泛型限制,只能添加String,添加int报错
System.out.println("list2的长度是:" + list2.size()); // 此时list2长度为1
/*
* 2.然后通过反射添加元素方式,在运行期动态加载类,首先得到list1和list2
* 的类类型相同,然后再通过方法反射绕过编译器来调用add方法,看能否插入int
* 型的元素
*/
Class c1 = list1.getClass();
Class c2 = list2.getClass();
System.out.println(c1 == c2); // 结果:true,说明类类型完全相同
// 验证:我们可以通过方法的反射来给list2添加元素,这样可以绕过编译检查
try {
Method m = c2.getMethod("add", Object.class); // 通过方法反射得到add方法
m.invoke(list2, 20); // 给list2添加一个int型的,上面显示在编译器是会报错的
System.out.println("list2的长度是:" + list2.size()); // 结果:2,说明list2长度增加了,并没有泛型检查
} catch (Exception e) {
e.printStackTrace();
}
/*
* 综上可以看出,在编译器的时候,泛型会限制集合内元素类型保持一致,但是编译器结束进入
* 运行期以后,泛型就不再起作用了,即使是不同类型的元素也可以插入集合。
*/
}
}