List<?> 是所有 List<T> 的上界父类型,允许安全读取但禁止写入(除 null),而 List<String> 与 List<Integer> 因编译期类型隔离不可互赋,原始类型 List 则放弃类型检查。

因为 List<?> 是通配符类型,表示“某个未知但确定的类型”的集合,它在编译期被设计为所有 List<T> 的**上界父类型**;而 List<String> 和 List<Integer> 是两个互不相关的具体参数化类型,JVM 运行时虽擦除泛型,但编译器仍严格禁止它们之间直接赋值——这是类型安全的强制保障。
为什么 List<?> 可以接收任意 List<T>
List<?> 不代表“任意类型混合”,而是代表“某个固定但未知的类型”的只读视图。它的语义是:我能从里面安全地取出元素(作为 Object),但不能往里加(除 null 外)。这种设计让编译器可以允许:
-
List<String> strs = new ArrayList<>(); List<?> wild = strs;✅ -
List<Integer> nums = new ArrayList<>(); List<?> wild = nums;✅ - 后续调用
wild.get(0)返回Object,不会破坏类型安全 - 不允许
wild.add("x")或wild.add(123),避免混入非法元素
为什么 List<String> 不能赋给 List<Integer>
尽管二者运行时都是 ArrayList,但编译器把它们视为**完全独立、不可转换的类型**:
- 如果允许
List<String> s = new ArrayList<Integer>();,后续s.add("abc")就会把字符串塞进本该只存整数的容器 - 这会绕过泛型约束,导致
Integer i = list.get(0);在运行时抛出ClassCastException - Java 编译器在赋值检查阶段就拦截这种明显违背类型契约的操作
对比 List(原始类型)的特殊行为
原始类型 List 是泛型历史兼容的产物,不是类型安全的设计:
立即学习“Java免费学习笔记(深入)”;
-
List raw = new ArrayList<String>();允许,但触发警告 -
List<Integer> safe = raw;也允许(unchecked assignment),但风险自担 - 它本质是放弃编译期检查,把类型安全责任推给开发者
- 而
List<?>是有约束的、安全的向上转型;List<String>到List<Integer>是无约束的、危险的跨类型赋值——编译器坚决不放行
真正安全的转换需要显式验证
若确实需将一个集合“当作”另一类型使用,必须自己承担校验责任:
- 用反射或遍历检查每个元素是否为目标类型实例
- 再配合
@SuppressWarnings("unchecked")强制转型 - 例如:
raw2generic(list, Integer.class)方法会在返回前逐个确认元素类型 - 跳过校验直接强转,等于主动关闭泛型保护机制


















