Java 泛型

最后更新: 2025-02-02 05:23:41 +0800 +0800
tags: Generics | Java
原文以英文撰写。您正在阅读AI翻译版本。

Java 泛型提供编译期类型检查,同时保持与泛型出现之前字节码的兼容性。 编译器会擦除类型参数,在需要的位置插入强制类型转换,并可能生成桥接方法, 以便擦除后仍能保留重写关系。因此,List<String> 和 List<Integer> 具有相同的运行时类, 并且无法可靠地从对象中恢复泛型类型参数。

不变性和边界 #

泛型类是不变的:List<Integer> 不是 List<Number>,因为后者允许插入 Double。 通配符在 API 边界表达变型。

java
static double sum(List<? extends Number> values) {
    return values.stream().mapToDouble(Number::doubleValue).sum();
}

static void addDefaults(List<? super Integer> destination) {
    destination.add(0);
}

使用 extends 表示从中读取的生产者,使用 super 表示向其中写入的消费者。 List<? extends Number> 可以产生 Number 值,但不接受非空值; List<? super Integer> 接受 Integer 值,但只能产生 Object。 “只读”和“只写”只是简写,并非能力规则:两种列表仍可被查询、清空, 或者在实现允许时接收 null。

捕获和推断 #

每个通配符都表示一个未知的捕获类型。通配符容器无法表达两个独立通配符值具有相同类型, 因此在关系重要时使用泛型辅助方法:

java
static void reverse(List<?> list) { reverseCaptured(list); }
private static <T> void reverseCaptured(List<T> list) {
    Collections.reverse(list);
}

方法推断会结合参数约束、目标类型和声明的边界。菱形运算符会为构造器调用推断类型参数; 但它不会使原始类型变得安全。边界可以要求特定能力: <T extends Comparable<? super T>> T max(List<? extends T> values)。 交集边界可以包含一个类,后面跟随多个接口。

擦除下的构造 #

new T()、new T[n] 和 T.class 都是非法的,因为擦除不会保留运行时所需的构造器、 组件类型或类字面量。应改为传入工厂:

java
static <T> T create(Supplier<? extends T> factory) {
    return factory.get();
}

对于反射集成,接受 Class<T> 并处理其受检查的反射异常;但如果由调用方负责构造, 优先使用 Supplier<T>。泛型数组仍受到限制,因为数组是具体化且协变的,而泛型类型会被擦除且具有不变性。

参考资料 #

评论

在 GitHub 上阅读或评论(发表评论需要登录)