数组协变导致ArrayStoreException仅在运行时发生,而泛型不变性使其错误在编译期就被捕获;数组允许String[]赋值给Object[],但写入不兼容类型时JVM检查失败抛异常,泛型则因类型无关而编译不通过。

Java 中数组支持协变,而泛型采用不变性,这是导致 ArrayStoreException 仅在运行时爆发、却不会在泛型中出现的根本原因。
数组协变允许“看似合法”的赋值
因为 String 是 Object 的子类,Java 规定 String[] 也是 Object[] 的子类型。这使得以下代码编译完全通过:
String[] strs = new String[2];Object[] objs = strs; // ✅ 编译成功 —— 协变生效
但此时 objs 引用的仍是底层为 String[] 的内存结构。编译器只认声明类型 Object[],不追踪实际运行时类型。
写入时才触发运行时检查
JVM 在每次执行 a[i] = x 时,会动态比对 x 的实际运行时类型与数组**真实组件类型**(而非引用声明类型)是否兼容:
objs[0] = "hello"; // ✅ String 是 String[] 的合法元素objs[1] = new Integer(42); // ❌ 运行时报 ArrayStoreException
这个异常不是编译器“拦住”了错误,而是 JVM 在存储动作发生后,发现 Integer 不是 String 的子类,直接中断执行。
泛型不变性把问题挡在编译期
泛型容器如 List 和 List 彼此无关,不存在继承关系:
Listlist = new ArrayList(); ListobjList.add(new Integer(1)); // 根本走不到这一步
即使绕过引用赋值,直接向 List 添加非 String 元素,也会被编译器当场拒绝,不会生成字节码。
协变的设计初衷与代价
数组协变诞生于泛型出现之前,目的是让通用工具方法(如 void printAll(Object[] arr))能接收任意对象数组,减少强制转型。但它用运行时风险换来了语法便利:
- 读操作安全:通过
Object[]读String[]总是 OK - 写操作危险:任何写入都可能因类型不匹配而崩溃
- 泛型无此妥协:从设计上放弃协变,换取全程类型可控










