? extends T 是协变通配符,用于安全读取T或其子类型实例,体现“生产者用extends”原则:可get(如Number n = list.get(0)),不可add(除null),因编译器无法确定具体子类型以保障类型安全。

上界通配符 ? extends T 的核心,是让一个泛型容器“安全地对外提供(produce)T类型或其子类型的实例”,所以叫 Producer Extends。它不关心容器里具体装的是哪个子类,只保证:你拿出来的东西,一定能当成 T 用。
它为什么叫“协变”?
协变本质是“子类型关系向上传递”。比如:Integer 是 Number 的子类 → 那么 List<Integer> 就可以被当作 List<? extends Number> 来用。这种“子类型容器能赋值给父类型通配符”的行为,就是协变 —— 类型关系方向没变,和继承方向一致。
-
List<Integer>、List<Double>、List<BigDecimal>都能赋给List<? extends Number> - 但
List<Number>不能赋给List<? extends Integer>(因为Number不是Integer的子类)
为什么只能读、不能写?
编译器不知道容器实际承载的是哪个具体子类型。你声明的是 List<? extends Number>,它可能是 List<Integer>,也可能是 List<Double>,甚至 List<AtomicInteger> —— 这些类型互不兼容。
-
读是安全的:无论里面是什么,拿出来都能向上转型成
Number,比如Number n = list.get(0); -
写是危险的:如果允许
list.add(new Double(3.14)),而底层其实是ArrayList<Integer>,就会破坏类型安全 —— 所以编译器直接禁止任何添加操作(除null)
典型 Producer 场景
当你设计一个“只从集合取数据”的方法时,就该用 ? extends T:
立即学习“Java免费学习笔记(深入)”;
- 计算数字总和:
double sum(List<? extends Number> nums)—— 接收List<Integer>、List<Float>都行 - 打印水果名称:
void printNames(List<? extends Fruit> fruits)—— 支持Apple、Banana等所有子类列表 - 工具方法如
Collections.max()参数就是Collection<? extends T>,因为它只读不改
记住一个画面
把 List<? extends T> 想成一个“只出不进”的管道出口:水(元素)只能从这里流出来,且流出的水一定是 T 或更“纯净”的子类型(比如 Integer 水),但你永远不知道管道里原先装的是哪一种纯净水 —— 所以绝不能往里倒别的水。


















