• 20220424 Java核心技术 卷1 基础知识 8


    泛型程序设计

    从 Java 程序设计语言 1.0 版发布以来, 变化最大的部分就是泛型。致使 Java SE 5.0 中增加泛型机制 的主要原因是为了满足在 1999 年制定的最早的 Java 规范需求之一(JSR 14 )。专家组花费了 5 年左右的时间用来定义规范和测试实现

    为什么要使用泛型程序设计

    泛型程序设计(Generic programming) 意味着编写的代码可以被很多不同类型的对象所重用。

    类型参数的好处

    泛型提供了 类型参数 (type parameters) 用来指示元素的类型:

    ArrayList<String> files = new ArrayList<String>():  
    

    在 Java SE 7及以后的版本中, 构造函数中可以省略泛型类型,省略的类型可以从变量的类型推断得出:

    ArrayList<String> files = new ArrayList();
    

    类型参数的魅力在于:使得程序具有更好的可读性和安全性。

    谁想成为泛型程序员

    AirayList 类有一个方法 addAll 用来添加另一个集合的全部元素。 程序员可能想要将 ArrayList<Manager> 中的所有元素添加到 ArrayList<Employee> 中去。然而, 反过来就不行了。如果只能允许前一个调用, 而不能允许后一个调用呢? Java 语言的设计者发明了一个具有独创性的新概念,通配符类型 (wildcard type) ,它解决了这个问题。通配符类型非常抽象,然而,它们能让库的构建者编写出尽可能灵活的方法。

    定义简单泛型类

    一个泛型类(generic class ) 就是具有一个或多个类型变量的类。

    public class Pair<T> {
        private T first;
        private T second;
    
        public Pair() {
            first = null;
            second = null;
        }
    
        public Pair(T first, T second) {
            this.first = first;
            this.second = second;
        }
    
        public T getFirst() {
            return first;
        }
    
        public T getSecond() {
            return second;
        }
    
        public void setFirst(T newValue) {
            first = newValue;
        }
    
        public void setSecond(T newValue) {
            second = newValue;
        }
    }
    

    泛型类可以有多个类型变量。

    public class Pair<T, U> { . . . }
    

    类定义中的类型变量指定方法的返回类型以及域和局部变量的类型。例如,

    private T first; // uses the type variable  
    

    类型变量使用大写形式,且比较短, 这是很常见的。在 Java 库中, 使用变量 E 表示集合的元素类型, KV 分别表示表的关键字与值的类型。T ( 需要时还可以用临近的字母 US ) 表示 “ 任意类型 ”。

    泛型类可看作普通类的工厂

    泛 型 方 法

    public class ArrayAlg {
        public static <T> T getMiddle(T... a) {
            return a[a.length / 2];
        }
    }
    

    类型变量放在修饰符(这里是 public static) 的后面,返回类型的前面。

    泛型方法可以定义在普通类中,也可以定义在泛型类中。

    当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型:

    String middleS = ArrayAlg.<String>getMiddle("]ohnM", "Q.", "Public");
    

    在这种情况(实际也是大多数情况)下,方法调用中可以省略 <String> 类型参数。编译器有足够的信息能够推断出所调用的方法。

    几乎在大多数情况下,对于泛型方法的类型引用没有问题。 偶尔, 编译器也会提示错误, 此时需要解译错误报告。

    double middleD = ArrayAlg.getMiddle(3.14, 1729, 0);
    

    报错信息:

    Required type: double
    Provided: Number & Comparable<? extends Number & Comparable<?>>
    

    如果想知道编译器对一个泛型方法调用最终推断出哪种类型,有这样一个窍门: 有目的地引入一个错误, 并研究所产生的错误消息。

    JButton result = ArrayAlg.getMiddle("Hello", 0, null);
    

    报错信息:

    Required type: JButton
    Provided: Serializable & Comparable<? extends Serializable & Comparable<?>>
    

    意思是: 可以将结果賦给 SerializableComparable<?>Comparable<? extends Serializable>

    类型变量的限定

    将 T 限制为实现了 Comparable 接口,可以通过对类型变量 T 设置 限定(bound ) 实现这一点:

    public static <T extends Comparable<T>> T min(T[] a) 
    

    在此为什么使用关键字 extends 而不是 implements ? 毕竟,Comparable 是一个接口。下面的记法

    <T extends BoundingType>
    

    表示 T 应该是绑定类型的 子类型 (subtype )。 T 和绑定类型可以是类, 也可以是接口。选择关键字 extends 的原因是更接近子类的概念, 并且 Java 的设计者也不打算在语言中再添加一个新的关键字。

    一个类型变量或通配符可以有多个限定, 例如:

    T extends Comparable & Serializable  
    

    在 Java 的继承中, 可以根据需要拥有多个接口超类型, 但限定中至多有一个类。如果用一个类作为限定,它必须是限定列表中的第一个。

    泛型代码和虚拟机

    虚拟机没有泛型类型对象,所有对象都属于普通类。

    类 型 擦 除

    无论何时定义一个泛型类型, 都自动提供了一个相应的 原始类型 ( raw type) 。原始类型的名字就是删去类型参数后的泛型类型名。擦除( erased ) 类型变量 , 并替换为限定类型 (无限定的变量用 Object ) 。

    public class Pair<T> {
        private T first;
        private T second;
    
        public Pair() {
            first = null;
            second = null;
        }
    
        public Pair(T first, T second) {
            this.first = first;
            this.second = second;
        }
    
        public T getFirst() {
            return first;
        }
    
        public T getSecond() {
            return second;
        }
    
        public void setFirst(T newValue) {
            first = newValue;
        }
    
        public void setSecond(T newValue) {
            second = newValue;
        }
    }
    

    擦除后,Pair<T> 的原始类型如下所示:

    public class Pair {
        private Object first;
        private Object second;
    
        public Pair() {
            first = null;
            second = null;
        }
    
        public Pair(Object first, Object second) {
            this.first = first;
            this.second = second;
        }
    
        public Object getFirst() {
            return first;
        }
    
        public Object getSecond() {
            return second;
        }
    
        public void setFirst(Object newValue) {
            first = newValue;
        }
    
        public void setSecond(Object newValue) {
            second = newValue;
        }
    }
    

    因为 T 是一个无限定的变量, 所以直接用 Object 替换。

    结果是一个普通的类, 就好像泛型引人 Java 语言之前已经实现的那样。

    在程序中可以包含不N类型的 Pair , 例 如, Pair<String>Pair<LocalDate> 。 而擦除类型后就变成原始的 Pair 类型了。

    原始类型用第一个限定的类型变量来替换, 如果没有给定限定就用 Object 替换。

    class Interval<T extends Serializable & Comparable> 原始类型是 Serializable ,如果交换位置,class Interval<T extends Comparable & Serializable> 原始类型是 Comparable

    编译器在必要时要插入强制类型转换。 为了提高效率, 应该将标签(tagging ) 接口(即没有方法的接口)放在边界列表的末尾。

    翻译泛型表达式

    当程序调用泛型方法时, 如果擦除返回类型, 编译器插入强制类型转换。

    Pair<Employee> buddies = ...
    Employee buddy = buddies.getFirst();
    

    擦除 getFirst 的返回类型后将返回 Object 类型。编译器自动插人 Employee 的强制类型转换。

    当存取一个泛型域时也要插人强制类型转换。假设 Pair 类的 first 域和 second 域都是公有的(也许这不是一种好的编程风格,但在 Java 中是合法的)。表达式:

    Employee buddy = buddies.first;  
    

    也会在结果字节码中插人强制类型转换。

    翻译泛型方法

    需要记住有关 Java 泛型转换的事实:

    • 虚拟机中没有泛型,只有普通的类和方法
    • 所有的类型参数都用它们的限定类型替换
    • 桥方法被合成来保持多态
    • 为保持类型安全性,必要时插人强制类型转换
    public class DateInterval extends Pair<LocalDate> {
        @Override
        public void setSecond(LocalDate second) {
            System.out.println("DateInterval.setSecond");
            if (second.compareTo(getFirst()) >= 0) {
                super.setSecond(second);
            }
        }
    
        @Override
        public LocalDate getSecond() {
            return super.getSecond();
        }
    
        @Override
        public DateInterval clone() throws CloneNotSupportedException {
            return (DateInterval) super.clone();
        }
    }
    

    DateInterval 里有两个 setSecond 方法,类型擦除后,两个方法的参数类型分别是 LocalDateObject (继承自 Pair

    问题在于类型擦除与多态发生了冲突。要解决这个问题, 就需要编译器在 Datelnterval 类中生成一个桥方法 (bridge method)

    public void setSecond(java.lang.Object)
    

    DateInterval 里同样有两个 getSecond 方法

    LocalDate getSecond()
    Object getSecond()
    

    这不符合 Java 的规范,是不合法的,但是 在虚拟机中,用参数类型和返回类型确定一个方法 。因此, 编译器可能产生两个仅返回类型不同的方法字节码,虚拟机能够正确地处理这一情况

    桥方法不仅用于泛型类型。也适用于在一个方法覆盖另一个方法时可以指定一个更严格的返回类型。 例如 clone 方法

    调用遗留代码

    @SuppressWarnings("unchecked") 注解 可以消除泛型警告。

    约束与局限性

    大多数限制都是由类型擦除引起的

    不能用基本类型实例化类型参数

    不能用类型参数代替基本类型。因此, 没有 Pair<double> , 只 有 Pair<Double> 。 当然,其原因是类型擦除。擦除之后, Pair 类含有 Object 类型的域, 而 Object 不能存储 double

    这样做与 Java 语言中基本类型的独立状态相一致,只有 8 种基本类型, 当包装器类型(wrapper type) 不能接受替换时, 可以使用独立的类和方法处理它们

    运行时类型查询只适用于原始类型

    if (a instanceof Pair<String>) // 编译错误
    
    Pair<String> p = (Pair<String>) a;	// 强制类型转换时警告
    
    Pair<String> stringPair = new Pair<>();
    Pair<Integer> integerPair = new Pair<>();
    
    System.out.println(stringPair.getClass() == integerPair.getClass());    // true
    

    不能创建参数化类型的数组

    // Pair<String>[] table = new Pair<String>[10]; // 编译错误
    

    声明类型为 Pair<String>[] 的变量仍是合法的。不过不能用 new Pair<String>[10] 初始化这个变量

    可以声明通配类型的数组, 然后进行类型转换:

    Pair<String>[] stringPairsArr = (Pair<String>[]) new Pair<?>[10];
    

    如果需要收集参数化类型对象, 只有一种安全而有效的方法: 使用 ArrayListArrayList<Pair<String>>

    Varargs 警告

    向参数个数可变的方法传递一个泛型类型的实例可能会出现问题

    public static <T> void addAll(Collection<T> coll, T... ts) {
        for (T t : ts) {
            coll.add(t);
        }
    }
    
    public static void main(String[] args) {
        Collection<Pair<String>> table = null;
        Pair<String> pair1 = null;
        Pair<String> pair2 = null;
        addAll(table, pair1, pair2);
    }
    

    这里在 main 方法里调用 addAll 会被警告

    Unchecked generics array creation for varargs parameter 
    

    可以采用两种方法来抑制这个警告。 一种方法是为包含 addAll 调用的方法增加注解 @SuppressWamings("unchecked") ;另一种方法是使用 @SafeVarargs

    对于只需要读取参数数组元素的所有方法,都可以使用这个 @SafeVarargs 注解,来消除创建泛型数组的有关限制

    不能实例化类型变置

    不能使用像 new T(...)newT[...]T.class 这样的表达式中的类型变量

    // 推荐
    public static <T> Pair<T> makePair(Supplier<T> constructor) {
        return new Pair<>(constructor.get(), constructor.get());
    }
    
    // 利用反射
    public static <T> Pair<T> makePair(Class<T> clazz) {
        try {
            return new Pair<>(clazz.newInstance(), clazz.newInstance());
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }
    

    注意,Class 类本身是泛型。 例如,String.class 是一个 Class<String> 的实例(事实上,它是唯一的实例 ) 。

    不能构造泛型数组

    就像不能实例化一个泛型实例一样, 也不能实例化泛型数组。

    如果数组仅仅作为一个类的私有实例域, 就可以将这个数组声明为 Object[] ,并且在获取元素时进行类型转换。例如
    ArrayList

    public static void main(String[] args) {
        // String[] minmax = minmax("a", "b", "c");
        String[] minmax2 = minmax2(String[]::new, "a", "b", "c");
        String[] minmax3 = minmax3("a", "b", "c");
    }
    
    /**
         * 编译警告,运行错误,ClassCastException
         *
         */
    public static <T extends Comparable> T[] minmax(T... a) {
        Object[] mm = new Object[2];
        // ...
        return (T[]) mm; // ClassCastException
    }
    
    /**
         * 推荐
         */
    public static <T extends Comparable> T[] minmax2(IntFunction<T[]> constr, T... a) {
        T[] mm = constr.apply(2);
        // ...
        return mm;
    }
    
    /**
         * 使用反射
         */
    public static <T extends Comparable> T[] minmax3(T... a) {
        T[] mm = (T[]) Array.newInstance(a.getClass().getComponentType(), 2);
        // ...
        return mm;
    }
    

    ArrayList 类的 toArray 有下面两种不同的形式:

    Object[] toArray()
    T[] toArray(T[] result) 
    

    第二个方法接收一个数组参数。 如果数组足够大, 就使用这个数组。 否则, 用 result 的成分类型构造一个足够大的新数组。

    T[] toArray(T[] result) 的逻辑:

    • 如果 result 数组长度小于 list 的长度,会返回一个新的结果数组,长度和内容与 list 相同
    • 如果 result 数组长度等于 list 的长度,会将 list 的内容覆盖赋值给 result
    • 如果 result 数组长度大于 list 的长度,会将 list 的长度内的内容覆盖赋值给 resultresult[list.size] 被赋值为 nulllist.size 之后的数组内容不会改变
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
    
        for (int i = 0; i < 6; i++) {
            System.out.println("===========" + i);
            String[] strArr = new String[i];
            Arrays.fill(strArr, "fill");
            String[] toArray = list.toArray(strArr);
            System.out.println("toArray::" + Arrays.toString(toArray));
            System.out.println("strArr::" + Arrays.toString(strArr));
            System.out.println(strArr == toArray);
        }
    }
    

    打印信息:

    ===========0
    toArray::[a, b, c]
    strArr::[]
    false
    ===========1
    toArray::[a, b, c]
    strArr::[fill]
    false
    ===========2
    toArray::[a, b, c]
    strArr::[fill, fill]
    false
    ===========3
    toArray::[a, b, c]
    strArr::[a, b, c]
    true
    ===========4
    toArray::[a, b, c, null]
    strArr::[a, b, c, null]
    true
    ===========5
    toArray::[a, b, c, null, fill]
    strArr::[a, b, c, null, fill]
    true
    

    泛型类的静态上下文中类型变量无效

    不能在静态域或方法中引用类型变量。

    public class Singleton<T> {
        private static T singlelnstance; // 编译报错
    
        public static T getSinglelnstance() // 编译报错
        {
            if (singlelnstance == null) // construct new instance of T
            return singlelnstance;
        }
    }
    

    不能抛出或捕获泛型类的实例

    既不能抛出也不能捕获泛型类对象。实际上, 甚至泛型类扩展 Throwable 及其子类都是不合法的。

    catch 子句中不能使用类型变量。

    不过, 在异常规范中使用类型变量是允许的。

    public static <T extends Throwable> void throwAs(Throwable e) throws T
    

    可以消除对受查异常的检查

    Java 异常处理的一个基本原则是, 必须为所有受查异常提供一个处理器。不过可以利用泛型消除这个限制。

    public static void main(String[] args) {
        Exception t = new Exception();
        Block.<RuntimeException>throwAs(t);
    }
    
    @SuppressWarnings("unchecked")
    public static <T extends Throwable> void throwAs(Throwable e) throws T {
        throw (T) e;
    }
    

    如果调用 Block.<RuntimeException>throwAs(t); 编译器就会认为 t 是一个非受查异常。

    注意擦除后的冲突

    当泛型类型被擦除时, 无法创建引发冲突的条件。

    public class Block<T> {
        
        public boolean equals(T t) {	// 编译错误
            return super.equals(t);
        }
    }
    

    类型擦除后,boolean equals(T t)boolean equals(Object obj) 冲突

    泛型规范说明还提到另外一个原则:“ 要想支持擦除的转换, 就需要强行限制一个类或类型变量不能同时成为两个接口类型的子类, 而这两个接口是同一接口的不同参数化” 。

    class Employee implements Comparable<Employee> {
        @Override
        public int compareTo(Employee o) {
            return 0;
        }
    }
    class Manager extends Employee implements Comparable<Manager> {	// 编译错误
        
    }
    

    这一限制与类型擦除的关系并不十分明确。毕竟,下列非泛型版本是合法的。

    class Employee implements Comparable {
        @Override
        public int compareTo(Object o) {
            return 0;
        }
    }
    
    class Manager extends Employee implements Comparable {
        @Override
        public int compareTo(Object o) {
            return 0;
        }
    }
    

    泛型类型的继承规则

    考虑一个类和一个子类, 如 EmployeeManagerPair<Manager>Pair<Employee> 的一个子类吗? 答案是 “ 不是

    无论 S 与 T 有什么联系 ,通常, Pair<S>Pair<T> 没有什么联系。

    必须注意泛型与 Java 数组之间的重要区别。可以将一个 Manager[] 数组賦给一个类型为 Employee[] 的变量

    Pair<Manager> managePair = new Pair<>();
    Pair<Employee> employeePair = new Pair<>();
    
    // employeePair = managePair; // 编译错误
    
    Manager[] managerArr = new Manager[3];
    Employee[] employeeArr = new Employee[3];
    employeeArr = managerArr;
    
    employeeArr[0] = new Manager();
    // employeeArr[1] = new Employee();    // 运行错误,ArrayStoreException,这里 employeeArr 指向的是 Employee[]
    
    
    Pair<Manager> managerBuddies = new Pair<>();
    Pair rawBuddies = managerBuddies; // OK
    rawBuddies.setFirst(new String());
    Manager first = managerBuddies.getFirst();  // 运行错误 ClassCastException
    

    永远可以将参数化类型转换为一个原始类型。例如,Pair<Employee> 是原始类型 Pair 的一个子类型。

    泛型类可以扩展或实现其他的泛型类。 例如, ArrayList<T> 类实现 List<T> 接口。这意味着, 一个 ArrayList<Manager>
    以被转换为一个 List<Manager> 。但是, 如前面所见, 一个 ArrayList<Manager> 不是一个 ArrayList <Employee>List<Employee>

    img

    通 配 符 类 型

    通配符概念

    通配符类型中, 允许类型参数变化。 例如, 通配符类型

    Pair<? extends Employee>
    

    表示任何泛型 Pair 类型, 它的类型参数是 Employee 的子类, 如 Pair<Manager>

    img

    Pair<Manager> managerBuddies = new Pair<>();
    Pair<? extends Employee> wildcardBuddies = managerBuddies; // OK
    // wildcardBuddies.setFirst(new Employee());   // 编译错误
    // wildcardBuddies.setFirst(new Manager());    // 编译错误
    Employee first = wildcardBuddies.getFirst();
    

    仔细看一看类型 Pair<? extends Employee>。其方法似乎是这样的:

    ? extends Employee getFirst()
    void setFirst(? extends Employee)
    

    这样将不可能调用 setFirst 方法。编译器只知道需要某个 Employee 的子类型,但不知道具体是什么类型。它拒绝传递任何特定的类型。毕竟?不能用来匹配。

    使用 getFirst 就不存在这个问题: 将 getFirst 的返回值赋给一个 Employee 的引用完全合法。

    通配符的超类型限定

    ? super Manager
    

    这个通配符限制为 Manager 的所有超类型。

    可以为方法提供参数, 但不能使用返回值。例如, Pair<? super Manager> 有方法

    void setFirst(? super Manager)
    ? super Manager getFirst()
    
    Pair<? super Manager> pair = new Pair<>();
    
    // pair.setFirst(new Object());    // 编译错误
    // pair.setFirst(new Employee());    // 编译错误
    // Manager first = pair.getFirst();    // 编译错误
    
    pair.setFirst(new Manager());
    Object first = pair.getFirst();
    

    带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取。

    img

    超类型限定的另一种应用:Comparable 接口本身就是一个泛型类型,如果计算一个 String 数组的最小值, T 就是 String 类型的, 而 StringComparable<String> 的子类型。但是, 处理一个 LocalDate 对象的数组时, 会出现一个问题。LocalDate 实现了 ChronoLocalDate ,而 ChronoLocalDate 扩展了 Comparable<ChronoLocalDate> 。因此, LocalDate 实现的是 Comparable<ChronoLocalDate> 而不是 Comparable<LocalDate>

    如果需要兼容,可以声明如下方法:

    public static <T extends Comparable<? super T>> T min(T[] a)
    

    这样,Comparable<? super T>compareTo 方法写成

    int compareTo(? super T)  
    

    子类型限定的另一个常见的用法是作为一个函数式接口的参数类型。例如 Collection 接口的 removeIf 方法:

    boolean removeIf(Predicate<? super E> filter)
    

    无限定通配符

    可以使用无限定的通配符, 例如,Pair<?> ,有以下方法:

    ? getFirst()
    void setFirst(?) 
    

    getFirst 的返回值只能赋给一个 ObjectsetFirst 方法不能被调用, 甚至不能用 Object 调用(可以调用 setFirst(null) )。Pair<?>Pair 本质的不同在于: 可以用任意 Object 对象调用原始 Pair 类的 setObject 方法

    Pair<?> pair = new Pair<>();
    // pair.setFirst("xxx");   // 编译错误
    // pair.setFirst(new Object());   // 编译错误
    pair.setFirst(null);
    

    为什么要使用这样脆弱的类型? 它对于许多简单的操作非常有用。例如,下面这个方法将用来测试一个 pair 是否包含一个 null 引用,它不需要实际的类型。

    // 可读性更强
    public static boolean hasNulls1(Pair<?> p) {
        return p.getFirst() == null || p.getSecond() == null;
    }
    
    // 泛型方法
    public static <T> boolean hasNulls2(Pair<T> p) {
        return p.getFirst() == null || p.getSecond() == null;
    }
    

    通配符捕获

    编写一个交换成对元素的方法:

    public static void swap(Pair<?> p)  
    

    不能在编写代码中使用 ? 作为一种类型。 也就是说, 下述代码是非法的

    ? t = p.getFirst(); // 编译错误
    p.setFirst(p.getSecond()) ; 
    p.setSecond(t) ;
    

    这个问题有一个有趣的解决方案。我们可以写一个辅助方法 swapHelper

    public static void swap(Pair<?> p) {
        swapHelper(p);
    }
    
    public static <T> void swapHelper(Pair<T> p) {
        T t = p.getFirst();
        p.setFirst(p.getSecond());
        p.setSecond(t);
    }
    

    注意, swapHelper 是一个泛型方法, 而 swap 不是, 它具有固定的 Pair<?> 类型的参数。

    在这种情况下,swapHelper 方法的参数 T 捕获通配符

    通配符捕获只有在有许多限制的情况下才是合法的。编译器必须能够确信通配符表达的是单个、 确定的类型。 例如, ArrayList<Pair<T> 中的 T 永远不能捕获 ArrayList<Pair<?>> 中的通配符。数组列表可以保存两个 Pair<?> , 分别针对 ? 的不同类型。

    反射和泛型

    反射允许你在运行时分析任意的对象。 如果对象是泛型类的实例,关于泛型类型参数则得不到太多信息, 因为它们会被擦除。

    泛型 Class 类

    Class 类是泛型的。 例如, String.class 实际上是一个:Class<String> 类的对象(事实上,是唯一的对象) 。

    java.lang.Class<T> 方法名称 方法声明 描述
    newInstance T newInstance() 返回无参数构造器构造的一个新实例
    cast T cast(Object obj) 如果给定的类型确实是 T 的一个子类型,cast 方法就会返回一个现在声明为类型 T 的对象, 否则,抛出一个 BadCastException 异常
    getEnumConstants T[] getEnumConstants() 如果 T 是枚举类型, 则返回所有值组成的数组,否则返回 null
    getSuperclass Class<? super T> getSuperclass() 返回这个类的超类。如果 T 不是一个类或 Object 类, 则返回 null
    getConstructor Constructor<T> getConstructor(Class<?>... parameterTypes) 获得公有的构造器, 或带有给定参数类型的构造器
    getDeclaredConstructor Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 获得所有声明的构造器(包括私有), 或带有给定参数类型的构造器

    使用 Class<T> 参数进行类型匹配

    有时, 匹配泛型方法中的 Class<T> 参数的类型变量很有实用价值。 编译器可以根据参数推断出这个方法的返回类型:

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        Pair<String> stringPair = makePair(String.class);
    
    }
    
    public static <T> Pair<T> makePair(Class<T> c) throws InstantiationException, IllegalAccessException {
        return new Pair<>(c.newInstance(), c.newInstance());
    }
    

    虚拟机中的泛型类型信息

    java.lang.reflect 包中提供的接口 Type 包含以下子类型:

    • Class 类,描述具体类型
    • TypeVariable 接口,描述类型变量(如 T extends Comparable<? super T> )
    • WildcardType 接口, 描述通配符 (如 ? super T )
    • ParameterizedType 接口, 描述泛型类或接口类型(如 Comparable<? super T> )
    • GenericArrayType 接口, 描述泛型数组(如 T[]

    img

    与泛型相关的反射方法
    java.lang.Class<T> 方法名称 方法声明 描述
    getTypeParameters TypeVariable<Class<T>>[] getTypeParameters() 如果这个类型被声明为泛型类型, 则获得泛型类型变量,否则获得一个长度为 0 的数组
    getGenericSuperclass Type getGenericSuperclass() 获得被声明为这一类型的超类的泛型类型; 如果这个类型是 Object 或不是一个类类型 (class type),则返回 null
    getGenericInterfaces Type[] getGenericInterfaces() 获得被声明为这个类型的接口的泛型类型(以声明的次序) ,否则, 如果这个类型没有实现接口,返回长度为 0 的数组
    java.lang.reflect.Method 方法名称 方法声明 描述
    getTypeParameters TypeVariable<Method>[] getTypeParameters() 如果这个方法被声明为泛型方法, 则获得泛型类型变量,否则返回长度为 0 的数组
    getGenericReturnType Type getGenericReturnType() 获得这个方法被声明的泛型返回类型
    getGenericParameterTypes Type[] getGenericParameterTypes() 获得这个方法被声明的泛型参数类型。 如果这个方法没有参数, 返回长度为 0 的数组
    java.lang.reflect.TypeVariable 方法名称 方法声明 描述
    getName String getName() 获得类型变量的名字
    getBounds Type[] getBounds() 获得类型变量的子类限定,否则, 如果该变量无限定, 则返回长度为 0 的数组
    java.lang.reflect.WildcardType 方法名称 方法声明 描述
    getUpperBounds Type[] getUpperBounds() 获得这个类型变量的子类 ( extends ) 限定,否则, 如果没有子类限定,则返回长度为 0 的数组
    getLowerBounds Type[] getLowerBounds() 获得这个类型变量的超类( super ) 限定,否则, 如果没有超类限定, 则返回长度为 0 的数组
    java.lang.reflect.ParameterizedType 方法名称 方法声明 描述
    getRawType Type getRawType() 获得这个参数化类型的原始类型
    getActualTypeArguments Type[] getActualTypeArguments() 获得这个参数化类型声明时所使用的类型参数
    getOwnerType Type getOwnerType() 如果是内部类型, 则返回其外部类型, 如果是一个顶级类型, 则返回 null
    java.lang.reflect.GenericArrayType 方法名称 方法声明 描述
    getGenericComponentType Type getGenericComponentType() 获得声明该数组类型的泛型组件类型
    使用示例
    public class GenericReflectionTest {
    
        @SneakyThrows
        public static void main(String[] args) {
            // read class name from command line args or user input
            String name;
            if (args.length > 0) name = args[0];
            else {
                try (Scanner in = new Scanner(System.in)) {
                    System.out.println("Enter class name (e.g. java.util.Collections): ");
                    name = in.next();
                }
            }
    
            // print generic info for class and public methods
            Class<?> cl = Class.forName(name);
            printClass(cl);
            for (Method m : cl.getDeclaredMethods()) {
                printMethod(m);
            }
    
        }
    
        public static void printClass(Class<?> cl) {
            System.out.print(cl);
            printTypes(cl.getTypeParameters(), "<", ", ", ">", true);
            Type sc = cl.getGenericSuperclass();
            if (sc != null) {
                System.out.print(" extends ");
                printType(sc, false);
            }
            printTypes(cl.getGenericInterfaces(), " implements ", ", ", "", false);
            System.out.println();
        }
    
        public static void printMethod(Method m) {
            String name = m.getName();
            System.out.print(Modifier.toString(m.getModifiers()));
            System.out.print(" ");
            printTypes(m.getTypeParameters(), "<", ", ", "> ", true);
    
            printType(m.getGenericReturnType(), false);
            System.out.print(" ");
            System.out.print(name);
            System.out.print("(");
            printTypes(m.getGenericParameterTypes(), "", ", ", "", false);
            System.out.println(")");
        }
    
        public static void printTypes(Type[] types, String pre, String sep, String suf, boolean isDefinition) {
            if (pre.equals(" extends ") && Arrays.equals(types, new Type[]{Object.class})) return;
            if (types.length > 0) System.out.print(pre);
            for (int i = 0; i < types.length; i++) {
                if (i > 0) System.out.print(sep);
                printType(types[i], isDefinition);
            }
            if (types.length > 0) System.out.print(suf);
        }
    
        public static void printType(Type type, boolean isDefinition) {
            if (type instanceof Class) {
                Class<?> t = (Class<?>) type;
                System.out.print(t.getName());
            } else if (type instanceof TypeVariable) {
                TypeVariable<?> t = (TypeVariable<?>) type;
                System.out.print(t.getName());
                if (isDefinition) {
                    printTypes(t.getBounds(), " extends ", " & ", "", false);
                }
            } else if (type instanceof WildcardType) {
                WildcardType t = (WildcardType) type;
                System.out.print("?");
                printTypes(t.getUpperBounds(), " extends ", " & ", "", false);
                printTypes(t.getLowerBounds(), " super ", " & ", "", false);
            } else if (type instanceof ParameterizedType) {
                ParameterizedType t = (ParameterizedType) type;
                Type owner = t.getOwnerType();
                if (owner != null) {
                    printType(owner, false);
                    System.out.print(".");
                }
                printType(t.getRawType(), false);
                printTypes(t.getActualTypeArguments(), "<", ", ", ">", false);
            } else if (type instanceof GenericArrayType) {
                GenericArrayType t = (GenericArrayType) type;
                System.out.print("");
                printType(t.getGenericComponentType(), isDefinition);
                System.out.print("[]");
            }
        }
    }
    
  • 相关阅读:
    彻底理解cookie,session,token.md
    13Gin中使用jwt
    qstat f队列状态是au和s,如何恢复
    shell三剑客
    mysql根据俩个字段之差进行区间分组
    运行jnlp文件
    mount o后面参数含义
    Linux常用命令
    java VelocityEngine 属性key值得组成说明
    Linux 基本命令 sed
  • 原文地址:https://www.cnblogs.com/huangwenjie/p/16187670.html
Copyright © 2020-2023  润新知