12.9 泛型与反射API
在第2章中对反射API进行过详细的介绍。通过反射API所获取的是程序运行时的信息。泛型类型和泛型方法相关的信息在源代码中虽然存在,但是在编译时已经被擦除。在实际开发中,仍然有在运行时获取泛型相关信息的需求。为了满足这种需求,随着泛型的引入,J2SE 5.0对Java字节代码的格式进行了修改,把泛型相关的信息添加到字节代码中。不过字节代码中的泛型相关的内容只是提供相关的信息,不会对字节代码的运行造成影响。在程序中可以使用反射API来获取这些信息。
结合第8章对Java字节代码格式的介绍,泛型相关的信息是作为类、方法和域的可选属性来保存的。对应的属性名称是“Signature”。这个属性的值是一个表示类或接口、方法或域的泛型类型签名的字符串。类或接口的签名由形式类型参数、父类型的签名和所实现的接口的签名组成;方法的签名由形式类型参数、参数类型签名、返回值类型签名和声明的受检异常类型签名组成;域的签名是其引用的一般类型、数组类型或形式类型参数的签名。使用javap工具分析代码清单12-35中的泛型类对应的字节代码,可以得出GenericSignature类的类型签名是“<S:Ljava/lang/Number;>Ljava/lang/Object;”,由形式类型参数S、上界类型“Ljava/lang/Number;”和父类“Ljava/lang/Object;”组成;域obj的签名是“TS;”,其中“T”和“;”分别是形式类型参数的固定前缀和后缀,中间是形式类型参数“S”;方法get的签名是“(TS;)TS;”,其中参数类型和返回值类型都是表示形式类型参数“S”的“TS;”。
代码清单12-35 用来说明泛型类型、方法和域的签名的示例
public class GenericSignature<S extends Number>{
public S obj;
public void set(S obj){
this.obj=obj;
}
public S get(S obj){
return obj;
}
}
在不了解字节代码格式的情况下,可以通过反射API获取相同的信息。在使用反射API之前,先要获取泛型类型对应的Class类的对象,通过泛型类型的原始类型的class字面量可以得到这个对象,使用Class类的对象的getField和getMethod方法来获取表示域和方法的Field类和Method类的对象。
在Field类中,与泛型相关的方法是getGenericType。方法getGenericType的返回值是java.lang.reflect.Type接口的实现对象。Type接口是Java语言中所有类型都需要实现的接口。Type接口只是一个标记接口,其中并没有任何方法。Class类实现了Type接口,其他与泛型相关的类型接口java.lang.reflect.GenericArrayType、java.lang.reflect. ParameterizedType、java.lang.reflect.TypeVariable和java.lang.reflect.WildcardType都继承自Type接口。在得到一个Type接口的实现对象时,需要先判断具体的类型,再使用具体类型中对应的方法。
GenericSignature类中的域obj是一个类型参数。通过getGenericType方法返回的是Type接口的子接口TypeVariable的实现对象,表明这是一个类型参数。TypeVariable接口提供了3个方法来获取与类型参数相关的信息:getName方法返回的是类型参数在源代码中的名称;getGenericDeclaration方法返回的是声明该类型参数的类或方法;getBounds方法返回的是包含类型参数的所有上界的数组。
由于方法的参数、返回值和异常声明中都可以包含类型参数,Method类中的getGenericParameterTypes、getGenericReturnType和getGenericExceptionTypes方法分别用来获取表示这些类型的Type接口的实现对象。如果一个方法的参数类型是参数化类型,那么所得到的类型实际上是ParameterizedType接口的实现对象。ParameterizedType接口提供了3个方法来获取与参数化类型相关的信息:getActualTypeArguments方法用来获取参数化类型的实际类型;getOwnerType方法获取包含当前类型的父类型;getRawType方法获取对应的原始类型。当在参数化类型中使用了通配符时,实际的类型是WildcardType接口的实现对象。WildcardType接口中的getLowerBounds和getUpperBounds方法分别用来获取通配符的上界和下界。
代码清单12-36给出了使用反射API获取泛型信息的示例,围绕对类Target中的create方法的处理而展开。在得到表示create方法的Method类的对象之后,通过getGenericParameterTypes方法得到该方法的参数的类型是一个TypeVariable接口的实现对象。接着通过getGenericReturnType方法得到返回值类型是一个ParameterizedType接口的实现对象。该ParameterizedType接口的实现对象对应的参数化类型的实际类型是一个包含通配符的类型,由WildcardType接口的实现对象来表示。
代码清单12-36 使用反射API获取泛型信息的示例
class Target<T>{
public List<?extends Comparable<T>>create(T obj){
return null;
}
}
public void reflect()throws Exception{
Class<?>clazz=Target.class;
Method method=clazz.getMethod("create",new Class<?>[]{Object.class});
Type paramType=method.getGenericParameterTypes()[0];
TypeVariable<?>typeVariable=(TypeVariable<?>)paramType;
typeVariable.getName();//值为T
Type returnType=method.getGenericReturnType();
ParameterizedType pType=(ParameterizedType)returnType;
Type actualType=pType.getActualTypeArguments()[0];
Type[]bounds=((WildcardType)actualType).getUpperBounds();
ParameterizedType boundType=(ParameterizedType)bounds[0];
boundType.getRawType();//Comparable接口的Class类对象
}