学习笔记: Java语言中的Type

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个子接口,分别是ParameterizedTypeTypeVariable<D>GenericArrayTypeWildcardType,以及1个很常见的实现类Class
其中,ParameterizedType、GenericArrayType、Class用来表示泛型或普通类的Type,TypeVariable、WildcardType用来表示泛型中的类型参数的Type

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[] arrList<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下有三个子类,分别为ClassMethodConstructor。也就是说,我们定义泛型只能在一个类中这3个地方自定义泛型。
我们经常将类的属性(Field)声明为泛型,这其实并不是在定义泛型,而只是在使用泛型而已。此时的泛型变量,还是需要在Class中定义的,比如HashSet

关于 Type 对象

  1. 想知道Type对象表示的Class的名称(即某个泛型类或普通类),用Type.getTypeName()
  2. 想知道Type对象属于Type的哪个子类(即ParameterizedType、GenericArrayType 或 Class),用Type.getClass().getTypeName()Type.getClass().getName()
  3. 如果是泛型,想知道泛型中的类型参数,则在上一步确定属于ParameterizedType 或 GenericArrayType 后,将其强制转型,然后通过ParameterizedType 或 GenericArrayType的方法获得类型参数信息(TypeVariable 或 WildcardType)。

参考文档

  1. java知识总结之Type
  2. Java中的Type
  3. Java中的Type类型详解
  4. Java获取泛型T的类型 T.class
posted @ 2020-09-22 16:27  i江湖中人  阅读(686)  评论(0编辑  收藏  举报