21.3 反射与泛型

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)都可以有注解,注解到底是什么呢?让我们下章探讨。