? extends t用于只读(可get为t,不可add),? super t用于只写(可add t,get仅得object),遵循pecs原则:producer extends、consumer super。

泛型通配符里的 ? extends T 和 ? super T 不是语法糖,而是类型安全的“能力开关”。关键不在“它是什么”,而在“它能做什么”——extends 放开读取,锁死写入;super 放开写入,锁死精确读取。PECS 就是把这种分工总结成一句口诀:Producer Extends,Consumer Super。
上界通配符 ? extends T:只读不写,安全取值
表示“某个 T 类型或它的子类”,比如 List extends Number> 可以指向 ArrayList<integer></integer>、LinkedList<double></double>,甚至 ArrayList<bigdecimal></bigdecimal>。
- ✅ 能安全读:遍历拿到的每个元素,编译器保证是
Number或其子类,可直接调用doubleValue()、intValue()等方法 - ❌ 不能写:哪怕传一个
new Integer(5),编译器也不让——因为实际容器可能是Double列表,加进去会破坏类型一致性 - ⚠️ 注意:
get(0)返回的是Number,但set(0, new Integer(1))编译失败,add(...)同样禁止
下界通配符 ? super T:只写不精读,安全注入
表示“某个 T 类型或它的父类”,比如 List super Integer> 可以指向 ArrayList<number></number>、ArrayList<object></object>,甚至 LinkedList<serializable></serializable>(如果 Integer 实现了它)。
- ✅ 能安全写:任何
Integer实例(包括new Integer(42)、100自动装箱)都能添加进去,因为目标容器肯定能容纳Integer及其所有父类型 - ❌ 不能安全读:
get(0)只能赋给Object,不能直接当Integer用——因为你不知道它实际是Object还是Number,甚至可能是更宽泛的接口类型 - ⚠️ 注意:
Collection.<t>addAll()</t>内部就用了? super T,才能把任意T元素批量塞进目标集合
PECS 原则:按角色选通配符,不是按继承方向
别被“上界/下界”字面迷惑。真正决定用哪个的,是这个泛型参数在方法里扮演的角色:
- 如果你从它 拿东西出来用(比如求和、打印、校验),它是 生产者(Producer) → 用
? extends T - 如果你往它 塞东西进去存(比如添加、收集、归并),它是 消费者(Consumer) → 用
? super T - 如果既读又写,通常说明设计有问题——要么拆成两个参数,要么直接用具体类型
T
常见误用与避坑点
这些错误很典型,但编译器报错信息往往不够直白:
-
List extends String> list = new ArrayList(); list.add("hi");→ 编译失败。String 没有子类(final),但通配符逻辑不变:仍禁止写入 -
List super Number> list = new ArrayList<object>(); Number n = list.get(0);</object>→ 编译失败。只能接Object,要转成Number得手动强转(不推荐) - 不能用通配符声明泛型类或泛型方法的类型参数,例如
class Box extends T>是非法的 -
new ArrayList extends Number>()编译不过——通配符只用于变量、参数、返回值,不能用于实例化











