List<?>不是List<Object>:前者表示未知但确定的具体类型,禁止除null外的写入,读取安全依赖类型推理;后者是具体类型Object,允许任意写入但读取需强制转型,且不支持泛型边界。

无界通配符 ? 不是“万能Object”
很多人初看 List<?> 就觉得它和 List<Object> 差不多,都能装各种类型——其实这是误解。? 表示“某个确定但未知的具体类型”,而 Object 是一个真实存在的、最顶层的类。编译器对 ? 的处理是:知道它有唯一类型,只是不告诉你;对 Object 则明确知道它是所有类的父类,但放弃类型约束。
写操作限制完全不同
使用无界通配符时,向集合添加元素几乎被禁止(只允许 add(null)),因为编译器无法验证你加进去的东西是否匹配那个“未知但固定”的类型:
-
List<?> list = new ArrayList<String>();→ 合法 -
list.add("hi");→ 编译错误 -
list.add(null);→ 唯一允许的写入
而 List<Object> 完全开放写入权限,任何对象都能加,但代价是取出来时必须手动转型,且极易在运行时报 ClassCastException:
List<Object> objs = new ArrayList<>();-
objs.add("abc"); objs.add(42);→ 全部合法 -
Integer i = (Integer) objs.get(0);→ 运行时崩溃
读取行为看似相似,本质逻辑不同
两者都支持用 Object 接收读出的元素(Object o = list.get(0);),但原因不同:
-
List<?>中 get() 返回的是“那个未知类型的上界”,而 Java 规定所有引用类型的公共上界就是 Object,所以安全返回 Object -
List<Object>中 get() 返回的就是 Object 类型本身,没有类型推导过程,纯靠继承关系硬转
换句话说,? 的读安全来自类型系统推理,Object 的读“安全”只是表面——它把类型风险全部推迟到后续使用环节。
泛型边界能力是 Object 完全不具备的
无界通配符可自然升级为有界形式,比如 ? extends Number 或 ? super Integer,从而精准表达“只读数字类”或“可写整数及父类”的语义。Object 没有这种伸缩性:
-
void handleNums(List<? extends Number> nums)→ 只能读 Number 及其子类,类型安全 -
void handleNums(List<Object> nums)→ 什么都能读,但读完要自己判断是不是 Number,毫无保障
这种基于边界的协变/逆变控制,正是 PECS(Producer Extends, Consumer Super)原则的实践基础,Object 无法参与这套机制。

















