Java泛型默认不变,List<Cat>不是List<Animal>子类型;通配符? extends T用于只读场景(如List<? extends Number>),? super T用于只写场景(如List<? super Integer>),遵循PECS原则确保类型安全。

Java 泛型默认是不变的(invariant),也就是说,即使 Cat 是 Animal 的子类,List<Cat> 也不是 List<Animal> 的子类型。这种设计是为了防止运行时类型错误,但它也让很多合理场景下的代码无法编译。通配符(?、? extends T、? super T)就是为在不破坏类型安全的前提下,绕过这种僵硬限制而存在的。
用 ? extends T 放宽读取限制
当你只从集合中读取元素,不往里添加新内容时,上界通配符最合适:
-
List<? extends Number>可以接收List<Integer>、List<Double>等任意Number子类列表 - 从中遍历得到的每个元素,编译器能保证是
Number类型,可直接调用doubleValue()等方法 - 但禁止调用
add(...)—— 因为编译器不知道具体是哪个子类型,加Integer还是BigDecimal都可能出错
用 ? super T 放宽写入限制
当你只往集合中添加元素,不关心读出来是什么类型时,下界通配符更合适:
-
List<? super Integer>可以接收List<Integer>、List<Number>或List<Object> - 你可以安全地
add(new Integer(42)),因为Integer肯定能放进它的任何父类容器 - 但
get(0)返回的是Object,不能直接当Integer用,除非强转(且不推荐)
按 PECS 原则选对通配符
Producer-Extends, Consumer-Super 不是口诀,而是类型安全逻辑的总结:
立即学习“Java免费学习笔记(深入)”;
- 集合作为“生产者”(只往外提供数据)→ 用
? extends T - 集合作为“消费者”(只往里接收数据)→ 用
? super T - 比如
Collections.copy(dest, src):目标dest是消费者(? super T),源src是生产者(? extends T)
避免常见误用
通配符不是万能胶,乱用反而引入隐患:
- 不要把
List<?>当作“万能 List”来转型或强转——它只能调用Object方法,比如toString() - 别试图用
(List<String>) (List<?>) list强转,这绕过编译检查,运行时可能抛ClassCastException - 如果既需要读又需要写,通配符就不适用,直接用确定类型,如
List<T>或泛型方法


















