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.class与int.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 val或T 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.class | Void.Type | Void.class |
| byte.class | Byte.Type | Byte.class |
| short.class | Short.Type | Short.class |
| int.class | Integer.Type | Integer.class |
| long.class | Long.Type | Long.class |
| boolean.class | Boolean.Type | Boolean.class |
| char.class | Character.Type | Character.class |
| float.class | Float.Type | Float.class |
| double.class | Double.Type | Double.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类型的多样性,对含有复杂的继承关系以及泛型定义的实体类进行精准的类型解析,具体分析略(懒得抄书)。