java数组协变允许子类数组赋值给父类数组引用(如string[]→object[]),但运行时检查导致arraystoreexception;泛型不变确保编译期类型安全,禁止list与list互赋;协变通过通配符(如list
![java中 协变数组 `string[] extends object[]` 与不可变泛型 `list<string>` 的设计哲学对比](https://img.php.cn/upload/article/001/242/473/178409273722334.jpeg?x-oss-process=image/resize,p_40)
数组协变:为早期通用性妥协的运行时安全
Java 1.0 就支持数组协变,核心目标是让工具方法能“一招通吃”多种对象数组。比如 Arrays.toString(Object[]) 可直接接收 String[]、Integer[] 等任意对象数组,无需强制转型或重载。
这种设计依赖一个关键事实:只要子类能赋值给父类引用(String ≤ Object),那么对应数组类型也自动成立(String[] ≤ Object[])。编译器允许 Object[] objArr = new String[3],因为它只检查声明类型兼容性。
但代价是把类型检查推迟到运行时——JVM 每次执行 objArr[i] = x 都会核对 x.getClass() 是否属于数组底层真实组件类型(这里是 String)。一旦写入 new Integer(1),立刻抛出 ArrayStoreException。
泛型不变:用编译期严格换全程类型可控
泛型出现在 Java 5,此时已有大量强类型需求。设计者明确拒绝数组式协变,根本原因就一条:不希望 List
所以 List
- List
list = new ArrayList(); - List → 编译失败
- list.add(new Date()); → 编译失败
这背后是泛型擦除机制:运行时所有 List
协变不是不要,而是交给更安全的表达方式
Java 并没放弃协变能力,只是把它从“默认开启”改为“显式启用”,通过通配符实现有约束的向上兼容:
-
List extends Number> 可接收 List
或 List —— 协变读取安全 -
List super Integer> 可接收 List
或 List —— 逆变写入安全 - PECS 原则(Producer-Extends, Consumer-Super)指导你何时该用哪种
这种方式把“谁可以读、谁可以写”的语义清晰暴露在类型签名里,不再靠隐式继承关系埋下隐患。
本质差异不在语法,而在类型生命周期管理
数组是具体化(reified)的:它在运行时完整保留组件类型信息,所以能做 ArrayStore 检查;
泛型是类型擦除(erased)的:它在运行时丢失所有泛型参数,只靠编译期约束保障安全。
一个靠运行时兜底,一个靠编译期封堵——这不是优劣之分,而是不同阶段对“安全”与“便利”权重的不同取舍。数组协变是 Java 早期向后兼容与简易性的选择;泛型不变则是成熟生态中对健壮性和可维护性的主动加码。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











