通配符是编译期类型约束工具,非万能占位符;? extends t 适用于只读场景(可get、不可add),? super t 适用于只写场景(可add、get仅得object),遵循pecs原则。
通配符不是“万能占位符”,它本质是编译期的类型约束工具。? extends t 和 ? super t 的核心作用,是让方法参数能接收更宽泛的泛型类型,同时守住类型安全底线——但它们本身不能用于变量赋值或对象创建,也不能混用读写逻辑。
为什么 ? extends T 的集合不能 add 元素
编译器只知道这个集合装的是 “T 或它的某个子类”,但不确定具体是哪一个。比如 List extends Number> 可能是 List
- ✅ 安全操作:调用 get(),返回值可安全转为 T(如 Number n = list.get(0))
- ❌ 禁止操作:add() 除 null 外全部报错(null 虽合法,但无业务意义)
- ⚠️ 注意:这种写法只适合“只读”场景,比如遍历、统计、转换,不适合构建或修改集合
为什么 ? super T 的集合可以 add,但读取受限
? super T 表示“T 或它的某个父类”,比如 List super Integer> 可能是 List
- ✅ 安全操作:add(T 实例) 或其子类实例(如 add(new Long(100)) 不行,但 add(new AtomicInteger(5)) 可以,前提是它是 Integer 子类)
- ❌ 不安全操作:直接用 Integer i = list.get(0) 编译失败——你只保证“能装”,不保证“只装”,返回值只能是 Object
- ⚠️ 提示:读取后若需强转,必须自行判断类型(instanceof),这会削弱泛型优势,应尽量避免
PECS 原则落地:参数设计要分清角色
Producer Extends, Consumer Super —— 这不是口诀,是方法签名的设计铁律。关键看这个参数在方法里是“往外拿数据”还是“往里塞数据”。
- 往外拿(生产者)→ 用 ? extends T:如 void printAll(List extends CharSequence> list)
- 往里塞(消费者)→ 用 ? super T:如
void copy(List src, List super T> dest) - 错误示范:void process(List extends Runnable> tasks) { tasks.add(runnable); } → 编译不过,角色错配
常见误用与替代方案
很多人试图用通配符声明变量或返回值,结果陷入类型擦除和赋值困境。
- ❌ 错误:List super String> list = new ArrayList super String>(); → 编译失败,“cannot instantiate wildcard type”
- ❌ 错误:public List extends Number> getNumbers() { ... } → 调用方无法 add,也难向下转型,实用性低
- ✅ 推荐:方法参数用通配符放宽入参;返回值优先用具体泛型(如 List
)或泛型方法( List create())










