? super T 可写入T及其子类,因实际类型必为T或其父类,类型安全;? extends T 禁止写入(除null),因实际类型是未知的T子类,写入可能破坏类型约束。

为什么 ? super T 能往集合里写元素,而 ? extends T 不行
因为类型擦除后,JVM 需要确保写入安全:编译器只允许向声明为 ? super T 的集合中添加 T 或其子类实例,这样无论实际类型是 T、Object 还是某个中间父类(比如 Number),都能保证类型兼容。反过来,? extends T 表示“某个未知的 T 子类”,你无法确定它具体是什么,所以禁止写入(除了 null)——否则可能破坏集合的真实类型约束。
常见错误现象:Collection extends Number> nums = new ArrayList<integer>(); nums.add(new Double(1.0));</integer> 会编译失败,提示 “add(capture#1 of ? extends Number) in Collection cannot be applied to (Double)”。
Collection super Integer> 允许写入哪些值
只要值的静态类型是 Integer 或其子类(如 Long、Short 在显式转型后不被接受,但 Integer 实例本身可以),就能写入。注意:运行时类型无关紧要,关键看编译期类型检查。
-
Integer实例 —— 安全,直接允许 -
new Long(1)—— 不允许,因为Long不是Integer的子类(二者同属Number,但无继承关系) -
null—— 总是允许,因为它是所有引用类型的子类型 - 如果实际集合是
ArrayList<number></number>,那么写入Integer没问题;但如果集合是ArrayList<object></object>,也一样能接收Integer,因为Integer是Object的子类
典型使用场景:泛型工具方法中做“消费者”操作
最常见的是类似 Collections.copy() 或自定义的批量添加逻辑,目标集合需要接收源数据,但又不想限定死具体父类类型。
立即学习“Java免费学习笔记(深入)”;
例如这个工具方法:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
public static <T> void addAll(Collection<? super T> dest, T... items) {
for (T item : items) {
dest.add(item); // ✅ 编译通过
}
}
调用时可以灵活传入不同父类型的集合:
addAll(new ArrayList<number>(), new Integer(1), new Integer(2));</number>addAll(new ArrayList<object>(), "hello", 42); // 若 T 是 Object,则 OK;若 T 是 String,则 42 会报错</object>- 但不能写成
addAll(new ArrayList<string>(), new Integer(1))</string>—— 因为Integer不是String的子类,违反? super T约束
容易忽略的坑:读取返回值的类型是 Object,不是 T
虽然你能往 Collection super T> 里安全地写 T,但从中读取元素时,编译器只知道它是个 Object(因为上界是未知父类),不能自动转成 T。
比如:
Collection<? super Integer> c = new ArrayList<Number>(); c.add(123); Integer i = c.iterator().next(); // ❌ 编译错误:Type mismatch: cannot convert from Object to Integer
必须显式转型:
Integer i = (Integer) c.iterator().next(); // ✅ 但有 ClassCastException 风险
所以,? super T 天然适合「只写不读」或「读取后手动处理」的场景。如果既要写又要读且希望类型安全,应避免通配符,改用具体泛型参数(如 <t> Collection<t></t></t>)或者用有界类型变量(如 <u super t></u> 不合法,需换思路)。

















