Java泛型具有不变性,即List<String>不是List<Object>的子类型,尽管String是Object的子类;为绕过该限制并保障类型安全,需使用通配符:<? extends T>用于只读(如List<? extends Number>可取Number但不可add),<? super T>用于只写(如List<? super Integer>可add Integer但get返回Object),<?>表示完全未知类型。

Java 泛型的不可变性(Invariance)是面试中常被混淆但极易踩坑的核心概念。它不是指“不能修改对象”,而是指泛型类型之间**不存在继承关系**——哪怕其类型参数本身有父子类关系,泛型容器也不自动继承。
为什么 List<String> 不是 List<Object> 的子类型?
这是不可变性的典型体现。虽然 String 是 Object 的子类,但 List<String> 和 List<Object> 在 Java 中互不兼容,不能赋值、不能强转、也不能作为参数直接传递给期望 List<Object> 的方法。
原因在于类型安全:如果允许 List<String> stringList = new ArrayList<>(); List<Object> objList = stringList;,那么你就能往 objList 里 add(new Date()),这会破坏 stringList 的类型契约,导致运行时隐患——而泛型的设计目标正是在编译期杜绝这类问题。
如何用通配符绕过不可变性限制?
不可变性 ≠ 无法协作。Java 提供了通配符机制,在保持类型安全的前提下实现灵活读写:
立即学习“Java免费学习笔记(深入)”;
-
List<? extends Number>:只读视图。可安全获取元素(返回类型是Number),但不能 add 除null外的任何值(因为编译器不知道具体是 Integer 还是 Double)。 -
List<? super Integer>:只写视图。可安全 addInteger或其子类(如Long若存在),但 get() 返回的是Object,需谨慎处理。 -
List<?>:完全未知类型,只能当作List<Object>的弱化版使用(get 得到 Object,add 只能加 null)。
常见错误写法与辨析要点
面试官常通过以下代码考察是否真正理解不可变性:
✘ 错误示例:void process(List<Object> list) { /* ... */ }
List<String> strs = new ArrayList<>();
process(strs); // 编译失败!辨析关键:泛型类型是否可替换,看的是整个类型签名是否字面一致或满足通配符约束,而非底层元素类型的继承链。
正确做法是改方法签名:
- 若只需读取 → 改为
void process(List<? extends Object> list)(等价于List<?>); - 若需添加任意 Object → 改为
void process(List<Object> list),调用方必须传真正的List<Object>; - 更通用方案 → 改为泛型方法:
<T> void process(List<T> list)。
和协变(Covariance)、逆变(Contravariance)的关系
Java 数组是协变的(String[] 是 Object[] 的子类型),所以能写 Object[] arr = new String[1];,但这导致运行时 arr[0] = new Date() 报 ArrayStoreException —— 安全性后移。
而泛型选择不可变 + 通配符显式声明,把灵活性和安全性都交给开发者在编译期决定。这就是为什么说“泛型比数组更安全,但更严格”。


















