Java泛型通配符的核心作用是安全接收多种具体泛型集合而不破坏类型约束:? extends T用于只读场景(如List<? extends Number>可接收List<Integer>等,get返回Number但不可add),? super T用于只写场景(如List<? super Integer>可接收List<Number>等,可add Integer但get仅返回Object),裸?仅支持Object级操作。

Java 中泛型通配符在集合传参时的核心作用,是让方法能安全接收多种具体泛型类型的集合,而不破坏类型约束。关键不是“绕过”报错,而是用对通配符,让编译器认可这种宽泛但安全的传参方式。
为什么直接传子类泛型会报错?
比如你写了 List<Integer>,想传给一个参数为 List<Number> 的方法——这会编译失败。因为 List<Integer> 和 List<Number> 在 Java 里没有继承关系(泛型不具备协变性)。编译器怕你往 List<Number> 里加 Double,结果实际指向的是 List<Integer>,那就乱套了。
用 ? extends T 实现只读场景的安全传参
当你只需要从集合里读数据(比如遍历、计算、打印),就用上限通配符:
-
List<? extends Number>表示“里面元素是Number或它的任意子类”,比如List<Integer>、List<Double>、List<BigDecimal>都能传进来 - 你可以调用
get(),返回值自动推为Number,安全使用doubleValue()等方法 - 但不能调用
add()(除了null),因为编译器不知道你该加哪种具体子类型
示例:
立即学习“Java免费学习笔记(深入)”;
void printNumbers(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n.doubleValue()); // ✅ 安全
}
// list.add(3.14); // ❌ 编译错误
}
用 ? super T 实现只写场景的安全传参
当你主要往集合里塞数据(比如收集结果、填充缓存),就用下限通配符:
-
List<? super Integer>表示“里面元素类型是Integer或它的任意父类”,比如List<Integer>、List<Number>、List<Object>都合法 - 你可以安全地
add(new Integer(42))或add(new Long(100))吗?不行——只能加Integer及其子类(如AtomicInteger),不能加Long,因为Long不是Integer的子类 - 但读出来的元素只能当
Object用,因为不确定具体是哪个父类
示例:
立即学习“Java免费学习笔记(深入)”;
void collectIntegers(List<? super Integer> sink) {
sink.add(100); // ✅ 安全:Integer 是目标类型的子类型
sink.add(200); // ✅ 同上
// Integer i = sink.get(0); // ❌ 编译错误:返回类型是 Object
Object obj = sink.get(0); // ✅ 只能这样接
}
别用裸 ?,除非你真只要 Object 操作
List<?> 表示“某个未知类型”,它只允许做三件事:
- 调用返回
Object的方法(如get()、size()) - 调用不依赖泛型的方法(如
clear()、isEmpty()) - 写入
null(因为null属于任何引用类型)
它几乎没有业务价值,一般只出现在泛型工具方法内部或反射场景,日常传参应优先选 extends 或 super。


















