java数组协变是语言硬性设计,object[]可引用string[]等,但运行时写入类型不符元素会抛arraystoreexception;泛型不变,list不能赋值给list以保障类型安全。

Java数组为什么能用父类引用指向子类数组?
因为Java数组是协变的——这是语言层面的硬性设计,不是可选特性。比如 Object[] 可以直接引用 String[] 或 Integer[],编译器允许:
Object[] arr = new String[5]; // ✅ 编译通过这种赋值成立的前提是:如果
B extends A,那么 B[] 就是 A[] 的子类型。
ArrayStoreException 是怎么冒出来的?
协变带来的风险在运行时才暴露。数组在创建时就“记住”了自己真实的元素类型,每次写入都会做运行时检查:
-
Object[] arr = new Integer[3];→ 实际类型是Integer[] -
arr[0] = "hello";→ 编译通过,但运行抛出ArrayStoreException: java.lang.String - 而
arr[0] = 42;就没问题,因为int会自动装箱为Integer
这个检查是数组独有的——它靠运行时类型信息兜底,所以Java当年敢放开协变;而 ArrayList 这类泛型集合没这能力,只能靠编译期堵死。
为什么 List 不能赋给 List
泛型是不变的(invariant),和数组走的是两条路:
立即学习“Java免费学习笔记(深入)”;
-
List<string> list = new ArrayList<object>();</object></string>→ 编译直接报错 - 原因很实在:如果允许,你就能往
List<object></object>里加Float,结果却偷偷操作着一个本该只存String的容器 - Java 5 引入泛型时,刻意放弃协变来守住类型安全底线;后来用通配符补位,比如
List extends Object>才支持只读协变场景
什么时候该用数组协变?又该躲着走?
真实项目里,协变基本只在两类地方还能见到:
- 调用老API:比如
Arrays.asList(T...)、Arrays.equals(Object[], Object[]),它们依赖Object[]统一入口 - 反射或底层工具类:如
Method.invoke(obj, Object[] args),需要把任意参数数组转成Object[] - 但只要涉及写入、泛型交互、或需要长期持有,立刻换成
ArrayList或带通配符的泛型 —— 协变不是便利,是妥协留下的裂缝
最容易被忽略的一点:协变只存在于数组本身,不传递给其元素。哪怕你用 Number[] 接收 Integer[],往里塞 Double 依然会崩,而且崩得比泛型更晚、更难调试。









