? 是无界通配符,表示未知具体类型,非真实类型,故不可 new 或 add;仅支持 get()(返回 Object)、size() 等不涉泛型的操作。

为什么 ? 不能直接 new 或赋值具体类型
因为 ? 是无界通配符,编译器只知道它是「某个未知的、具体的类型」,但完全不确定是哪个。它不是类型,而是一个占位符——所以你不能用 new ArrayList<?>(),也不能写 list.add("str")(编译报错 add(capture#1-of ?))。这和 Object 不同:ArrayList<Object> 可以存任何引用类型,而 ArrayList<?> 连存一个 String 都不被允许。
常见错误现象:
- 试图调用
list.add(new Object())报错:「The method add(capture#1-of ?) is not applicable」 - 把
ArrayList<String>赋给ArrayList<?>没问题,反过来却编译失败
真正能用的只有读操作:get() 返回的是 Object,安全;size()、isEmpty() 这类不涉及泛型参数的方法也 OK。
? extends T 适合只读场景,但别误以为能取到 T 子类的具体类型
上界通配符表示「某个 T 的子类型(含 T 自身)」,比如 List<? extends Number> 可以指向 ArrayList<Integer> 或 LinkedList<Double>。但它只承诺「你能安全地从中读出 Number」,而不是「你能拿到 Integer 或 Double 的具体实例」。
立即学习“Java免费学习笔记(深入)”;
关键限制:
-
get(0)返回的是Number,不是Integer—— 即使底层确实是Integer,编译器也不让你当Integer用(除非显式强转) -
add()依然禁止:你无法确定该往里塞Integer还是Double,所以连add(null)都不被允许 - 适用于函数参数:比如
void printNumbers(List<? extends Number> list),明确表达「我只读,且只关心它们是数字」
? super T 适合只写场景,但 get() 返回的只是 Object
下界通配符表示「某个 T 的父类型(含 T 自身)」,比如 List<? super Integer> 可以指向 ArrayList<Number>、ArrayList<Object>,甚至 ArrayList<Integer>。它的核心价值在于「你能安全地写入 T 及其子类」。
典型使用场景:
- 作为方法参数接收「能装得下
Integer的容器」,比如Collections.<T>addAll(Collection<? super T> c, T... elements) - 你可以放心调用
c.add(new Integer(42)),因为无论c实际是ArrayList<Number>还是ArrayList<Object>,都接受Integer - 但
c.get(0)只能声明为Object,因为编译器只知道它至少是Object,没法推断更具体的类型
别混淆 extends 和 super 的方向,PECS 是经验,不是语法约束
PECS(Producer Extends, Consumer Super)是记忆技巧,不是语言规则。它提醒你:如果你要从集合「取东西」(Producer),用 extends;如果要往里「塞东西」(Consumer),用 super。但实际选择取决于你对数据流向的控制需求,而不是泛型声明本身。
容易踩的坑:
- 写
List<? extends Number> list = new ArrayList<Integer>(); list.add(new Integer(1));—— 编译失败,和「它是 Integer」无关,只因编译器拒绝任何写入 - 误以为
List<? super Integer>能接收Number:不行,super约束的是「容器类型必须是 Integer 的父类」,不是「能塞进去的元素类型」 - 在返回值中滥用通配符:比如方法返回
List<? extends CharSequence>,调用方反而更难处理,不如直接返回List<String>或抽象为接口
最常被忽略的一点:通配符解决的是「多态兼容性」,不是类型擦除的绕过手段。运行时所有泛型信息都已消失,ArrayList<String> 和 ArrayList<Integer> 在 JVM 层面都是 ArrayList —— 通配符只影响编译期检查,不改变字节码行为。









