? super T 定义写入的安全边界:只允许添加T及其子类实例,读取时返回Object;如List

Java 中 ? super T 通配符本身不是用来“限制”写入,而是**定义写入的安全边界**——它允许你往集合里写 T 类型或其子类的实例,同时禁止写入任何“比 T 更宽泛”的类型(比如父类),从而在编译期守住类型安全。
只允许写入 T 及其子类
编译器知道:无论实际集合是 ArrayList<object></object>、ArrayList<number></number> 还是 ArrayList<integer></integer>,它们都能容纳 Integer 实例。所以只要静态类型是 Integer(或它的子类,如匿名子类实例),就能安全添加。
-
✅ 允许:
list.add(new Integer(42))、list.add(100)(自动装箱为 Integer) -
❌ 不允许:
list.add(new Double(3.14))(Double 不是 Integer 的子类) -
❌ 不允许:
list.add(new Number() { })(Number 是父类,不满足“T 或子类”) -
✅ 总是允许:
list.add(null)(null 是所有引用类型的子类型)
读取时只能得到 Object
因为真实类型可能是 Object、Number 或 Integer,编译器无法确定具体是什么,所以 get() 返回值类型被擦除为 Object。
-
Object obj = list.get(0);—— ✅ 合法 -
Integer i = list.get(0);—— ❌ 编译报错,类型不匹配
不能写入父类,哪怕逻辑上“看起来合理”
这是最容易踩的坑:有人觉得 List super Integer> 既然能接受 Object,那写个 Number 应该也行?不行。原因很直接:
立即学习“Java免费学习笔记(深入)”;
- 通配符表达的是“某个未知的具体父类”,比如它可能是
ArrayList<integer></integer>(即实际类型就是 Integer) - 向
ArrayList<integer></integer>写Number显然不安全,会破坏泛型契约 - 所以编译器宁可保守:只认死理——必须是 T 或其子类
典型工具方法中的体现
像 Collections.addAll(Collection super T>, T...) 这类方法,正是靠这个规则实现灵活又安全的批量写入:
- 传入
new ArrayList<number>()</number>→ 可以加Integer、Long(如果 T 是 Number) - 传入
new ArrayList<object>()</object>→ 同样可以加Integer(因为 Integer 是 Object 子类) - 但不会让你误传
new ArrayList<string>()</string>→ 因为 String 不是 Integer 的父类,违反? super Integer


















