• 学习笔记: 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
  • 相关阅读:
    Oracle的分区打点
    学习Struts2经验总结
    优化MVC,实现数据库表的记录的添加、删除、修改、查询。
    基于struts研究传值问题
    基于“MVC”框架集设计模式,开发用户管理系统!
    使用Struts,实现简单的登录
    QT学习4:使用窗口部件
    QT学习9:绘制函数
    QT学习8:准备战斗
    QT学习6:组装丰富的积木!
  • 原文地址:https://www.cnblogs.com/haycheng/p/13712988.html
Copyright © 2020-2023  润新知