Java数组协变性允许子类型数组赋值给父类型数组引用,但运行时会因实际组件类型检查而抛出ArrayStoreException;应优先使用泛型集合替代,避免类型错配。

Java数组的协变性看似方便,实则埋着运行时异常的隐患。关键不在于能不能写,而在于编译通过不代表运行安全——Object[]变量指向实际为String[]或Integer[]的数组时,一旦存入不兼容类型,ArrayStoreException立刻抛出。
协变性如何悄悄引入风险
数组协变允许子类型数组赋值给父类型数组引用,比如:
-
Object[] objArr = new String[5];✅ 编译通过 -
objArr[0] = new Integer(42);❌ 运行时抛出ArrayStoreException
问题根源在于:数组在运行时保留其**实际组件类型**(component type),而协变仅放宽了编译期检查。JVM会在存储时校验元素是否属于该实际类型或其子类,否则中断执行。
常见高危场景识别
以下写法表面合理,却极易触发异常:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
- 用
Object[]接收泛型集合转出的数组(如list.toArray()返回Object[],但若原始 list 是List<String>,底层可能复用内部数组) - 将
Number[]引用指向Integer[],再试图存入Double或Float - 方法参数声明为
Object[],调用方传入Boolean[],方法内误存"true"
安全替代方案推荐
规避协变风险的核心是切断“引用宽泛、实际窄小”的错配:
- 避免用父类型数组引用接收具体子类型数组,改用泛型集合(如
List<String>)承载数据 - 必须使用数组时,显式创建目标类型数组:
String[] arr = new String[10];,而非Object[] arr = new String[10]; - 若需通用容器,优先选
ArrayList<Object>等可变集合,它不依赖运行时类型检查,add 操作始终安全 - 对遗留接口返回的
Object[],先确认其真实类型(如通过arr.getClass().getComponentType()),再决定能否写入
为什么泛型不走这条路
泛型设计为不变(invariant),正是吸取了数组协变的教训。例如 List<String> 和 List<Object> 无继承关系,编译器直接阻止错误赋值。这种严格性换来的是编译期即可拦截的类型错误,而不是让程序跑起来再崩溃。

















