java.lang.reflect.Type接口详解

3,610 阅读7分钟

Type接口详解

全限定名为java.lang.reflect.Type

Type is the common superinterface for all types in the Java programming language. These include raw types, parameterized types, array types, type variables and primitive types.

Type接口是Java中所有「类型」的父接口,包括了「原始类型」、「参数化类型」、「数组类型」、「类型变量」以及「基本类型」。

在Java中,除了方法之外的所有类型信息,包含了类、接口、父类、成员变量、常量、静态变量、方法出入参等,全部都可以用Type来归纳。

原始类型「Raw types」

其实就是java.lang.Class<?>,可以表示JVM中的一个类或接口,每个Java类在JVM中都表现为一个Class对象。所有元素类型相同且维度相同的数组在JVM中也都表现为同一个Class对象。「枚举」是一种「类」,而「注解」则是一种「接口」,在JVM中各自表示也都是类与接口的相应Class对象。关键字void在JVM中也表现为一个Class对象,且void有对应的Class类表示,即java.lang.Void

在Java中,所有定义的类与接口,包括枚举与注解,都在JVM中表示为Class对象,也就是说这些定义的类与接口其「类型」都属于「原始类型 Raw types」

基本类型「Primitive types」

byte short int long float double char boolean这八种基本类型关键字,它们也有对应的Class类型,但区别于对应包装类的Class类型。它们的类型表现方式为byte.class short.class int.class /* 省略 */。对于Integer.classint.class,有:Objects.equals(Integer.class, int.class) == false

参数化类型「Parameterized types」

表示为带有泛型的参数,比如List<String> Map<String,Long> Callable<T>。定义类型无法定义为参数化类型(定义类型指的是代码中写class A<T> 或 interface B<T>这种情况),这种情况下只能取得「类型变量 TypeVariable」这种类型的Type数组,表示为尖括号内定义的所有参数类型。除了这种情况,任何带有尖括号泛型的写法(即成员变量、接口、超类、方法出入参等)都可以被认定为是参数化类型。

实例说明:

// 以下几种写法无法表现为参数化类型,只能表现为Class类型,且可以取到TypeVariables类型的「类型变量」数组
class User<T> {} 
class Amap<K, V> {}
interface Human<E> {}

// 但这种写法下,超类或超类接口却可以被认定为参数化类型
// Staff<T> 与 Human<T> 都算是「参数化类型」
class User<T> extends Staff<T> implements Human<T> {}

// 有以下代码:
class A<T> extends ArrayList<String> implements Callable<T> {
    int count;
    String name;
    String[] ids;
    int[] arr;
    Map<String, Integer> map;
    List<T> tList;
    T[] array;
    Callable<Integer>[] futures;
    T get(T origin) { return origin; }
    void func(T t, Map<T, String> aMap) { }
    @Override public T call() throws Exception { return null; }
}
// 通过以下测试代码执行后:
@Test
void f() throws Throwable {
    Type type1 = A.class.getGenericSuperclass();
    System.out.printf("A.class.getGenericSuperclass(): %s%n%n", type1.getClass().getSimpleName());

    for (Type interfaceType : A.class.getGenericInterfaces()) {
        System.out.printf("Super interface type: %s / %s\n\n",
                interfaceType.getTypeName(), interfaceType.getClass().getSimpleName());
    }

    for (Field f : A.class.getDeclaredFields()) {
        System.out.printf("fieldName:      %s%n", f.getName());
        System.out.printf("fieldType:      %s%n", f.getGenericType().getTypeName());
        System.out.printf("fieldTypeClass: %s%n", f.getGenericType().getClass().getSimpleName());
        System.out.printf("fieldSuperclass: %s%n", f.getClass().getSuperclass().getSimpleName());
        if (f.getGenericType() instanceof GenericArrayType) {
            // 数组类型
            Type componentType = ((GenericArrayType) f.getGenericType()).getGenericComponentType();
            System.out.printf("fieldElementType: %s; fieldElementTypeClass: %s%n",
                    componentType.getTypeName(), componentType.getClass().getSimpleName());
        }
        System.out.println();
    }

    for (Method m : A.class.getDeclaredMethods()) {
        System.out.printf("methodName: %s%n", m.getName());
        System.out.printf("methodReturnType: %s%n", m.getGenericReturnType().getTypeName());
        System.out.printf("methodReturnTypeClass: %s%n", m.getGenericReturnType().getClass().getSimpleName());
        System.out.printf("methodParams: [%s]%n%n", Arrays.stream(m.getGenericParameterTypes())
                .map(type -> type.getTypeName() + ":" + type.getClass().getSimpleName())
                .collect(Collectors.joining(", ")));
    }
}
/* 执行结果:

A.class.getGenericSuperclass(): ParameterizedTypeImpl

Super interface type: java.util.concurrent.Callable<T> / ParameterizedTypeImpl

fieldName:      count
fieldType:      int
fieldTypeClass: Class
fieldSuperclass: AccessibleObject

fieldName:      name
fieldType:      java.lang.String
fieldTypeClass: Class
fieldSuperclass: AccessibleObject

fieldName:      ids
fieldType:      java.lang.String[]
fieldTypeClass: Class
fieldSuperclass: AccessibleObject

fieldName:      arr
fieldType:      int[]
fieldTypeClass: Class
fieldSuperclass: AccessibleObject

fieldName:      map
fieldType:      java.util.Map<java.lang.String, java.lang.Integer>
fieldTypeClass: ParameterizedTypeImpl
fieldSuperclass: AccessibleObject

fieldName:      tList
fieldType:      java.util.List<T>
fieldTypeClass: ParameterizedTypeImpl
fieldSuperclass: AccessibleObject

fieldName:      array
fieldType:      T[]
fieldTypeClass: GenericArrayTypeImpl
fieldSuperclass: AccessibleObject
fieldElementType: T; fieldElementTypeClass: TypeVariableImpl

fieldName:      futures
fieldType:      java.util.concurrent.Callable<java.lang.Integer>[]
fieldTypeClass: GenericArrayTypeImpl
fieldSuperclass: AccessibleObject
fieldElementType: java.util.concurrent.Callable<java.lang.Integer>; fieldElementTypeClass: ParameterizedTypeImpl

methodName: get
methodReturnType: T
methodReturnTypeClass: TypeVariableImpl
methodParams: [T:TypeVariableImpl]

methodName: func
methodReturnType: void
methodReturnTypeClass: Class
methodParams: [T:TypeVariableImpl, java.util.Map<T, java.lang.String>:ParameterizedTypeImpl]

methodName: call
methodReturnType: T
methodReturnTypeClass: TypeVariableImpl
methodParams: []
*/

类型变量「Type variables」

用于反映JVM编译泛型前的信息,也就是泛型符号的表示。在编译时,会被转换为一个具体的类型后才能正常使用。因为泛型实际上是一个编译期的特性,会被编译器「擦除」。

同时这种情况下也可以表示为变量的Type类型:private T valT get()void set(T val)

提到类型变量与擦除,同时这里记录以下Java1.5以后加入的新特性:桥接方法。桥接方法与泛型同时推出,是为了维护Java的多态性而设计的。当父类含有类型变量时,如果一个出入参为类型变量的方法被子类使用具体的类型所覆盖,那么JVM会自动为该子类添加一个与父类相同签名的桥接方法(通常表现为出入参类型是Object),因为父类的类型信息会被擦除,泛型符号被Object所替换。

数组类型「Array types」

组成元素为参数化类型或者类型变量的数组,比如List<String>[] Callable<R>[] T[]这样的定义方式。该接口只有一个方法,就是返回数组的组成元素的Type类型。如果数组的元素类型是普通的Class类型,那么该数组的类型也属于Class类型。

基本类型「Primitive types」

八种基本类型以及void关键字分别对应了自己的Class类型:

基本类型class等价于对应的包装类型class
void.classVoid.TypeVoid.class
byte.classByte.TypeByte.class
short.classShort.TypeShort.class
int.classInteger.TypeInteger.class
long.classLong.TypeLong.class
boolean.classBoolean.TypeBoolean.class
char.classCharacter.TypeCharacter.class
float.classFloat.TypeFloat.class
double.classDouble.TypeDouble.class

等价的意思是指Objects.equals(void.class, Void.Type) == true。但基本类型class与对应包装类型class却并不相同。

基本类型的定义方式是通过Class类的本地方法来实现的:

/*
 * Return the Virtual Machine's Class object for the named
 * primitive type.
 */
static native Class<?> getPrimitiveClass(String name);

通配符类型「Wildcard type」

例如Class<?> anyType中的?List<? extends Number>中的? extends Number、还有? super Integer。这几种都被定义为WildcardType类型。获取的方式则是通过将宿主类型转换为ParameterizedType后通过方法Type[]#getActualTypeArguments来获取。所有泛型的默认上边界为Object,下边界默认为空,也就是没有.

下边界的定义只可以出现在? super Integer这种使用情况,而不能在定义泛型变量时使用。Java中也只能在类或接口声明以及方法声明时定义泛型变量。

用处

mybatis的类型解析

在mybatis中有一个工具类:org.apache.ibatis.reflection.TypeParameterResolver。这个类提供了三个静态公共方法:

public class TypeParameterResolver {
  // 解析成员属性类型
  public static Type resolveFieldType(Field field, Type srcType) { ... }
  // 解析方法返回值类型
  public static Type resolveReturnType(Method method, Type srcType) { ... }
  // 解析方法参数类型
  public static Type[] resolveParamTypes(Method method, Type srcType) { ... }
}

这个类可以解析实体类的成员属性类型、方法的参数类型以及方法的返回值类型,以便完成JDBC查询结果到实体对象的映射。第一个参数为要解析的成员属性或方法,第二个参数则为查询到第一个参数的起始位置(比如在父类中定义一个私有id字段,而子类继承父类后,虽然无法直接访问到该字段,但确实可以从子类Class对象中找到id字段的Field对象,我们称子类为查询id字段的起始位置,也就是这里的srcType;同时父类也就是id字段的定义位置)。

以上三个方法实际上都会调用同一个内部方法:

/**
 * @param type 要解析的目标Type
 * @param srcType 查询起点Type
 * @param declaringClass 该目标字段/方法定义所在的Class
 */
private static Type resolveType(Type type, Type srcType, Class<?> declaringClass) {
  if (type instanceof TypeVariable) {
    return resolveTypeVar((TypeVariable<?>) type, srcType, declaringClass);
  } else if (type instanceof ParameterizedType) {
    return resolveParameterizedType((ParameterizedType) type, srcType, declaringClass);
  } else if (type instanceof GenericArrayType) {
    return resolveGenericArrayType((GenericArrayType) type, srcType, declaringClass);
  } else {
    return type;
  }
}

在方法内部中,mybatis会利用到Type类型的多样性,对含有复杂的继承关系以及泛型定义的实体类进行精准的类型解析,具体分析略(懒得抄书)。