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 Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
立即学习“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 依然会崩,而且崩得比泛型更晚、更难调试。
















