? extends T 是协变通配符,支持安全读取(返回类型为T或其父类)但禁止写入(除null外),本质是编译期类型约束,常用于只读通用方法如sumNumbers、max等。

Java 中的 ? extends T 是协变通配符,它让泛型容器支持“向上转型”式的多态读取——即用更宽泛的类型(如 List<? extends Number>)安全地引用更具体的子类型集合(如 List<Integer> 或 List<Double>),同时**只允许读取、禁止写入**(除 null 外)。它的核心机制不是“运行时多态”,而是编译期类型安全约束。
为什么能实现安全读取?
编译器知道:无论实际是 Integer、Double 还是 BigDecimal,它们都继承自 Number。所以从 List<? extends Number> 中调用 get(0),返回值类型被保守推断为 Number(或其父类 Object),这在任何子类型实例中都成立。
例如:
List<Integer> ints = Arrays.asList(1, 2, 3); List<? extends Number> numbers = ints; // ✅ 合法赋值 Number n = numbers.get(0); // ✅ 安全:Integer 是 Number 的子类 // numbers.add(4.5); // ❌ 编译错误:不能确定具体类型,禁止写入
读取时的类型收敛规则
使用 ? extends T 后,你能安全读出的类型是 T 及其所有父类,但无法获得原始具体类型信息:
立即学习“Java免费学习笔记(深入)”;
-
get(i)返回类型是T(不是? extends T); - 可以赋给
T、T的父类(如Object)或接口(如Serializable); - 不能直接强转为某个具体子类(如
(Integer) numbers.get(0)),除非你额外做运行时检查(如instanceof)。
典型应用场景
协变通配符常用于设计只读操作的通用方法,提升 API 兼容性:
- 聚合统计:接受任意数字列表并求和
double sumNumbers(List<? extends Number> list) {
return list.stream()
.mapToDouble(Number::doubleValue)
.sum();
}
// 可传入 List<Integer>、List<Float>、List<BigDecimal> 等
- 只读工具方法:打印元素、查找最大值等
<T extends Comparable<? super T>> T max(List<? extends T> list) {
return list.stream().max(Comparable::compareTo).orElse(null);
}
// 支持 List<String>、List<LocalDate> 等
注意边界:不能写入,也不能用作返回泛型参数
因为编译器无法确认你要添加的元素是否匹配未知的具体子类型:
-
add(E)、set(int, E)等写入方法参数类型被擦除为capture#1-of ? extends T,与任何具体类型都不兼容; - 不能用
? extends T声明泛型方法的返回类型参数(如<? extends Number> method()是非法语法),只能用于变量、形参、字段等使用位置。


















