20230531 8. 泛型程序设计

泛型程序设计

Java 5 中泛型的引入成为 Java 语言自最初发行以来最显著的变化。Java 的一个主要设计目的是支持之前版本的向后兼容性。因此 Java 的泛型有一些局限性。

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

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

类型参数的好处

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

var files = new ArrayList<String>():  

// 菱形语法
ArrayList<String> files = new ArrayList<>();

谁想成为泛型程序员

ArrayList 类有一个方法 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("John", "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 语言之前已经实现的那样。

在程序中可以包含不同类型的 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 方法

精彩!桥方法对应判断 Method.isBridge

调用遗留代码

设计 Java 泛型时,主要目标是允许泛型代码和遗留代码之间能够互操作。

@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;	// 强制类型转换时警告

getClass() 返回原始类型

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 注解

@SafeVarargs 只能用于声明为 static 、 final 或(Java 9 中)private 的构造器和方法。

@SafeVarargs 可以用来消除创建泛型数组的有关限制

不能实例化类型变量

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

在 Java 8 后,最好的解决办法是让调用者提供一个构造器表达式

// 推荐
public static <T> Pair<T> makePair(Supplier<T> constructor) {
    return new Pair<>(constructor.get(), constructor.get());
}

// 调用
Pair<String> p = makePair(String::new);

传统方法,利用反射

// 利用反射
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;
    }
}

// 调用
Pair<String> p = makePair(String.class);

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

不能构造泛型数组

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

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());       // 可以是 Manager 或 Manager 的子类型
Object first = pair.getFirst();     // 只能是 Object

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

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)

这样声明可以支持传参 Predicate<E>Predicate<Object>

无限定通配符

可以使用无限定的通配符, 例如,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 捕获通配符,变成了一个明确的类型 T

通配符捕获只有在非常有限的情况下才是合法的。编译器必须能够确信通配符表达的是单个确定的类型。 例如, ArrayList<Pair<T> 中的 T 永远不能捕获 ArrayList<Pair<?>> 中的通配符。可能保存两个 Pair<?>? 可能有不同的类型。

反射和泛型

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

泛型 Class 类

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

使用 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 泛型的突出特性之一是在虚拟机中擦除泛型类型,但是擦除的类仍然保留原先泛型的一些微弱记忆。例如,原始 Pair 类知道它源自于泛型类 Pair<T> ,尽管无法区分是 Pair<String> 还是 Pair<Integer>

为了描述泛型类型声明,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("[]");
        }
    }
}

类型字面量

可以捕获 Type 接口的一个实例,然后构造一个匿名子类

var type = new TypeLiteral<ArrayList<Integer>>() {}; // type 是 TypeLiteral 的匿名子类型

如果运行时有一个类型参数,可以将它与 TypeLiteral 匹配,我们无法从一个对象得到泛型类型,已经被擦除。不过,字段和方法参数的泛型类型还留存在虚拟机中。

/**
 * @version 1.02 2021-05-30
 * @author Cay Horstmann
 */

import org.junit.jupiter.api.Test;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

/**
 * A type literal describes a type that can be generic, such as
 * ArrayList<String>.
 */
class TypeLiteral<T> {
    private Type type;

    /**
     * This constructor must be invoked from an anonymous subclass
     * as new TypeLiteral<. . .>(){}.
     */
    public TypeLiteral() {
        Type parentType = getClass().getGenericSuperclass();
        if (parentType instanceof ParameterizedType paramType) {
            type = paramType.getActualTypeArguments()[0];
            System.out.println("type :: " + type);
        } else {
            throw new UnsupportedOperationException("Construct as new TypeLiteral<. . .>(){}");
        }
    }

    private TypeLiteral(Type type) {
        this.type = type;
    }

    /**
     * Yields a type literal that describes the given type.
     */
    public static TypeLiteral<?> of(Type type) {
        return new TypeLiteral<Object>(type);
    }

    public String toString() {
        if (type instanceof Class clazz) {
            return clazz.getName();
        } else {
            return type.toString();
        }
    }

    public boolean equals(Object otherObject) {
        return otherObject instanceof TypeLiteral otherLiteral && type.equals(otherLiteral.type);
    }

    public int hashCode() {
        return type.hashCode();
    }
}

/**
 * Formats objects, using rules that associate types with formatting functions.
 */
class Formatter {
    private Map<TypeLiteral<?>, Function<?, String>> rules = new HashMap<>();

    /**
     * Add a formatting rule to this formatter.
     *
     * @param type             the type to which this rule applies
     * @param formatterForType the function that formats objects of this type
     */
    public <T> void forType(TypeLiteral<T> type, Function<T, String> formatterForType) {
        rules.put(type, formatterForType);
    }

    /**
     * Formats all fields of an object using the rules of this formatter.
     *
     * @param obj an object
     * @return a string with all field names and formatted values
     */
    public String formatFields(Object obj) throws IllegalArgumentException, IllegalAccessException {
        var result = new StringBuilder();
        for (Field f : obj.getClass().getDeclaredFields()) {
            result.append(f.getName());
            result.append("=");
            f.setAccessible(true);
            Function<?, String> formatterForType = rules.get(TypeLiteral.of(f.getGenericType()));
            if (formatterForType != null) {
                // formatterForType has parameter type ?. Nothing can be passed to its apply
                // method. Cast makes the parameter type to Object so we can invoke it.
                @SuppressWarnings("unchecked") Function<Object, String> objectFormatter =
                        (Function<Object, String>) formatterForType;
                result.append(objectFormatter.apply(f.get(obj)));
            } else {
                result.append(f.get(obj).toString());
            }
            result.append("\n");
        }
        return result.toString();
    }
}

public class TypeLiterals {
    public static class Sample {
        ArrayList<Integer> nums;
        ArrayList<Character> chars;
        ArrayList<String> strings;

        public Sample() {
            nums = new ArrayList<>();
            nums.add(42);
            nums.add(1729);
            chars = new ArrayList<>();
            chars.add('H');
            chars.add('i');
            strings = new ArrayList<>();
            strings.add("Hello");
            strings.add("World");
        }
    }

    private static <T> String join(String separator, ArrayList<T> elements) {
        var result = new StringBuilder();
        for (T e : elements) {
            if (result.length() > 0) {
                result.append(separator);
            }
            result.append(e.toString());
        }
        return result.toString();
    }

    public static void main(String[] args) throws Exception {
        var formatter = new Formatter();
        formatter.forType(new TypeLiteral<ArrayList<Integer>>() {
        }, lst -> join(" ", lst));
        formatter.forType(new TypeLiteral<ArrayList<Character>>() {
        }, lst -> "\"" + join("", lst) + "\"");
        System.out.println(formatter.formatFields(new Sample()));
    }

    @Test
    public void test1() {
        var type = new TypeLiteral<ArrayList<Integer>>() {
        };

        System.out.println(type.getClass());

        var type2 = new TypeLiteral<ArrayList<Integer>>();
        System.out.println(type2.getClass());
    }
}

posted @   流星<。)#)))≦  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 易语言 —— 开山篇
· 实操Deepseek接入个人知识库
· Trae初体验
历史上的今天:
2020-01-13 20200113 SpringBoot整合MyBatis
点击右上角即可分享
微信分享提示