Java 数组是协变的(Dog[] 能赋给 Animal[]),这是编译期就放行的设计;但代价是运行时必须真实做一次类型检查兜底。这篇用真实的越界赋值场景,把 ArrayStoreException 的触发条件跑一遍。
数组协变允许 Dog[] 赋值给 Animal[],编译器看到的静态类型是 Animal[],但数组对象本身记得自己的真实元素类型。
ArrayCovarianceDemo.java Dog[] dogs = new Dog[3]; Animal[] animals = dogs; // 数组协变 animals[0] = new Cat(); // 编译通过,运行时才检查
animals.getClass(): class [LArrayCovarianceDemo$Dog; animals 实际运行时元素类型仍是 Dog(存在数组对象头里) 真实捕获 ArrayStoreException: ArrayCovarianceDemo$Cat animals[1] 赋值 Dog 成功: ArrayCovarianceDemo$Dog@7e9e5f8a
真实结果验证了数组协变的完整代价:animals.getClass() 显示这个数组对象的运行时类型真实是 Dog[],即使变量声明的静态类型是 Animal[]。编译器允许协变赋值是因为 Animal[] 只是一个"看待方式",数组对象本身携带的真实元素类型信息从未改变;每次写入数组时,JVM 都会真实拿这个元素类型和被写入的对象做一次 instanceof 级别的检查,不匹配就真实抛出 ArrayStoreException——这也是数组和泛型集合(编译期擦除、运行时不检查)在类型安全策略上的根本区别。
不只是自定义类继承关系,JDK 内置类型之间的数组协变同样真实受限。
Object[]=String[] 场景下,塞 Integer 真实捕获 ArrayStoreException: java.lang.Integer
把 String[] 赋值给 Object[] 变量是最常见的协变场景之一(很多接受 Object[] 参数的 API 内部就可能这样传入),真实往里面塞一个 Integer 同样会立刻触发运行时检查失败。
arraycopy 是一个 native 方法,容易被误以为是纯内存拷贝、不做类型检查——真实测一下。
ArrayCovarianceDemo.java Dog[] src = new Dog[]{new Dog(), new Dog()}; Object[] dst = new Cat[2]; System.arraycopy(src, 0, dst, 0, 2);
System.arraycopy 同样真实抛出 ArrayStoreException: arraycopy: type mismatch: can not copy ArrayCovarianceDemo$Dog[] into ArrayCovarianceDemo$Cat[]
每一步的成功/失败都是真实运行这段代码得到的结果,不是按规则手工推的。