Java在JDK 1.5之前只有原始类型而没有泛型(Generic)类型,而在1.5开始引入泛型。但这种泛型仅仅存在于编译阶段,当在JVM运行的过程中,与泛型相关的信息将会被擦除(采用类型擦除机制的原因,是因为如果在运行时存在泛型,那么将要修改JVM指令集,这是非常致命的)。
原始类型会生成字节码文件对象,而泛型类型相关的类型并不会生成与其相对应的字节码文件(因为泛型类型将会被擦除),因此无法将泛型相关的新类型与class相统一。
为了解决Java中的泛型,JDK1.5 版本开始引入Type接口。在此之前,Java中只有原始类型,所有的原始类型都是通过Class进行抽象。有了Type以后,Java的数据类型得到了扩展,从原始类型扩展为ParameterizedType、GenericArrayType、TypeVariable。
Java中的Type
接口位于包java.lang.reflect
下,属于反射相关的接口,它包括类、枚举、数组、注解等。该接口只有一个方法getTypeName
,用于获取该Type的名称。
很多开源项目的源码中会使用注解、反射、代理模式、适配器模式,它们都需要判断Type,因此有必要了解下Type。
Type有4个子接口,分别是ParameterizedType
、TypeVariable<D>
、GenericArrayType
、WildcardType
,以及1个很常见的实现类Class
。
其中,ParameterizedType、GenericArrayType、Class用来表示泛型或普通类的Type,TypeVariable
ParameterizedType
ParameterizedType
(参数化类型)是指我们常见的泛型的Type类,比如Set<E>
、List<String>
、TreeMap<K, V>
、Class<T>
等。
ParameterizedType
接口定义如下:
public interface ParameterizedType extends Type {
// 获取<>中实际的类型参数,以Type数组形式返回
Type[] getActualTypeArguments();
// 获取<>前面的类型,即包含泛型的类
Type getRawType();
// 如果这个类型是某个类型所属,则获取这个所有者的类型,否则返回null。比如Map.Entry<Sting,String>,会返回Map
Type getOwnerType();
}
GenericArrayType
GenericArrayType
(泛型数组类型)是指带有泛型的数组的Type类,比如 T[] arr
、List<String>[] lists
,这里的 arr、lists 就是 GenericArrayType 实例。
GenericArrayType
接口定义如下:
public interface GenericArrayType extends Type {
// 获得这个数组元素类型,比如T[]则获得T的type
Type getGenericComponentType();
}
Class
这是非泛型类的Type对象。类、接口、枚举、注解、数组都有一个Class对象。每个.class文件在运行期都对应一个Class对象。
TypeVariable<D>
TypeVariable<D extends GenericDeclaration>
(类型变量)指泛型类或泛型方法中的泛型,如 T a。TypeVariable<D>
与ParameterizedType
不同之处在于,後者是指整个泛型类,而前者专指泛型中参数类型。
TypeVariable<D>
接口定义如下:
public interface TypeVariable<D extends GenericDeclaration> extends Type, AnnotatedElement {
// 获取泛型的上限,无显示定义(extends)则默认为Object
Type[] getBounds();
// 获取声明该类型变量的实体(即获取类、方法或构造器名,如泛型中的容器类)
D getGenericDeclaration();
// 获取名称,即K、V、E之类名称
String getName();
AnnotatedType[] getAnnotatedBounds();
}
例如,对于Example<T extends Number & Serializable & Comparable>,getBounds()方法将得到一个class Number、interface Serializable、interface Comparable 构成的 Type 数组。
WildcardType
WildcardType
(通配符类型)是指<?>
、<? extends Number>
这样的表达式,它与TypeVariable
类似,都是用来指泛型中的参数类型。WildcardType虽然是Type的子接口,但却不是Java类型中的一种。
WildcardType
接口定义如下:
public interface WildcardType extends Type {
// 获取泛型表达式上界(对应extends后面的类型)
Type[] getUpperBounds();
// 获取泛型表达式下界(对应super后面的类型)
Type[] getLowerBounds();
}
例如,List<? extends Number> list1 的 getUpperBounds() 返回 Number;List<? super String> list2 的 getLowerBounds() 返回 String。
如何获取类的Type
对象
上述几种子接口,它们的实现类都不在Java基础类库中(而是在rt.jar的sun.reflect.generics.reflectiveObjects
包下),我们一般也不会自己实例化一个Type对象。
Class对象中有2个方法,可以获取Type对象:
getGenericSuperclass()
:获取该类直接父类的Type对象。如果该类只实现了某个接口,那么该方法返回的Type对象是Object的Type对象。getGenericInterfaces()
:获取该类直接实现的接口的Type数组
因此,我们如果想获取某个类或对象对应的Type对象,可以先得到该类的Class对象(类名.class
或 对象.getClass()
),然后通过上述两个方法获得它的直接父类或接口的Type对象。
另外,还可以根据实际的类型情况,将该Type对象(强制)转换成上述Type的子接口(ParameterizedType 或 GenericArrayType)或子类(Class),然后通过子类对象更详细的信息。
不过,这种方法获取的是类的父类或父接口的Type对象,不是当前类自己的Type对象。在Java的基础类库中没有获取当前类Type对象的方法,因此需要迂回一下。具体过程如下:
例如想获得类A的Type对象,先new出一个类A的匿名子类A sub = new A(){}
,然后通过sub.getGenericSuperclass()
就可以得到A的Type对象了。
这种迂回方法的一个问题是,A必须是具体的类才行得通,而不能是抽象类或接口(还得实现抽象方法,成本大),或者类似于ArrayList<? extends Number>>
这样带有通配符而无法实例化的类。
这种情况下,可以借助Google gson库中的类TypeToken来获取Type对象。该类与上面说的迂回方法类似,也是通过创建匿名子类来获取当前类的Type对象的。比如获取 HashMap<String, Integer> 的Type对象:Type type = new TypeToken<List<? extends Number>>(){}.getType()
。
另外,如果想获取类或对象中某个Field的Type对象、某个Method的入参或返回类型的Type对象、某个Constructor的入参的Type对象,还有如下方法:
Field#getGenericType
:获取域的Type对象Method#getGenericReturnType
:获取方法返回类型的Type对象Method#getGenericParameterTypes
:获取方法入参的Type对象数组Constructor#getGenericParameterTypes
:获取构造器入参的Type对象数组
Method和Constructor还能获取其所声明抛出的异常的Type对象数组。
在通过Class类getDeclaredField(String name)
方法(getField(String name)
只能获取public域)、getMethod()、getConstructor()等方法获得Field、Method、Constructor对象后,就可以进一步获取相关的Type对象了。
对于 TypeVariable 和 WildcardType,则可以通过 ParameterizedType 的 Type[] getActualTypeArguments() 方法获取参数类型列表,然后根据实际情况,将对应的Type对象强制转换成 TypeVariable 或 WildcardType 对象。
关于 GenericDeclaration
GenericDeclaration 是声明类型变量的所有实体的公共接口,也就是说该接口定义了哪些地方可以定义类型变量(泛型)。
GenericDeclaration下有三个子类,分别为Class
、Method
、Constructor
。也就是说,我们定义泛型只能在一个类中这3个地方自定义泛型。
我们经常将类的属性(Field)声明为泛型,这其实并不是在定义泛型,而只是在使用泛型而已。此时的泛型变量,还是需要在Class中定义的,比如HashSet
关于 Type 对象
- 想知道Type对象表示的Class的名称(即某个泛型类或普通类),用
Type.getTypeName()
。 - 想知道Type对象属于Type的哪个子类(即ParameterizedType、GenericArrayType 或 Class),用
Type.getClass().getTypeName()
或Type.getClass().getName()
。 - 如果是泛型,想知道泛型中的类型参数,则在上一步确定属于ParameterizedType 或 GenericArrayType 后,将其强制转型,然后通过ParameterizedType 或 GenericArrayType的方法获得类型参数信息(TypeVariable
或 WildcardType)。