21.3 反射与泛型 在介绍泛型的时候,我们提到,泛型参数在运行时会被擦除,这里,我们需要补充一下,在类信息Class中依然有关于泛型的一些信息,可以通过反射得到。泛型涉及一些更多的方法和类,上面的介绍中进行了忽略,这里简要补充下。
Class有如下方法,可以获取类的泛型参数信息:
1 public TypeVariable<Class<T>>[] getTypeParameters()
Field有如下方法:
1 public Type getGenericType ()
Method有如下方法:
1 2 3 public Type getGenericReturnType () public Type[] getGenericParameterTypes()public Type[] getGenericExceptionTypes()
Constructor有如下方法:
1 public Type[] getGenericParameterTypes()
Type是一个接口,Class实现了Type, Type的其他子接口还有:
TypeVariable:类型参数,可以有上界,比如T extends Number;
ParameterizedType:参数化的类型,有原始类型和具体的类型参数,比如List<String>
;
WildcardType:通配符类型,比如?、? extends Number、? superInteger。
我们看一个简单的示例,如代码清单21-2所示。
代码清单21-2 通过反射获取泛型信息示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class GenericDemo { static class GenericTest <U extends Comparable <U>, V> { U u; V v; List<String> list; public U test (List<? extends Number> numbers) { return null ; } } public static void main (String[] args) throws Exception { Class<? > cls = GenericTest.class; for (TypeVariable t : cls.getTypeParameters()) { System.out.println(t.getName() + " extends " + Arrays.toString(t.getBounds())); } Field fu = cls.getDeclaredField("u" ); System.out.println(fu.getGenericType()); Field flist = cls.getDeclaredField("list" ); Type listType = flist.getGenericType(); if (listType instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType) listType; System.out.println("raw type: " + pType.getRawType() + ", type arguments:" + Arrays.toString(pType.getActualTypeArguments())); } Method m = cls.getMethod("test" , new Class [] { List.class }); for (Type t : m.getGenericParameterTypes()) { System.out.println(t); } } }
程序的输出为:
1 2 3 4 5 U extends [java.lang.Comparable<U>] V extends [class java.lang.Object] U raw type: interface java.util.List, type arguments:[class java.lang.String] java.util.List<? extends java.lang.Number>
代码比较简单,我们就不赘述了。
本章介绍了Java中反射相关的主要类和方法,通过入口类Class,可以访问类的各种信息,如字段、方法、构造方法、父类、接口、泛型信息等,也可以创建和操作对象、调用方法等,利用这些方法,可以编写通用的、动态灵活的程序,本章演示了一个简单的通用序列化/反序列化类SimpleMapper。
反射虽然是灵活的,但一般情况下,并不是我们优先建议的,主要原因是: 1)反射更容易出现运行时错误,使用显式的类和接口,编译器能帮我们做类型检查,减少错误,但使用反射,类型是运行时才知道的,编译器无能为力。 2)反射的性能要低一些,在访问字段、调用方法前,反射先要查找对应的Field/Method,要慢一些。
简单地说,如果能用接口实现同样的灵活性,就不要使用反射 。
本章介绍的很多类(如Class、Field、Method、Constructor)都可以有注解,注解到底是什么呢?让我们下章探讨。