Java 协变数组类型
问题
假设现在有 Person 类和 Employee 类,假设 Employee IS-A Person,那么,这是不是意味着数组 Employee[ ] IS-A Person[ ] 呢?换句话说,如果一个例程接受 Person[ ] 作为参数,我们能不能把 Employee[ ] 作为作为参数来传递呢?
思考
乍一看,似乎 Employee[ ] 就应该和 Person[ ] 类型兼容。举个例子,假设除了 Employee 外,还有 Student IS-A Person,并假设 Employee[ ] 和 Person[ ] 是类型兼容的,此时考虑如下语句:
// 以下代码正常运行
Person[] arrayP1 = new Person[5];
arrayP1[0] = new Employee();
arrayP1[1] = new Student();
// 既然 Employee[] IS-A Person[]
// 那么意味着可将new Person[5]换为new Employee[5]
Person[] array = new Employee[5];
array[0] = new Employee();
array[1] = new Student();// ArrayStoreException
array[0] 成功引用一个 Employee,array[1] 本来应该引用一个 Employee,却引用了一个 Student,可是 Student IS-NOT-A Employee,这样就造成了类型混乱,又因为不存在类型转换,Java 虚拟机不能抛出 ClassCastException 异常。
Java 协变数组类型
避免上述问题最容易的方法是设定这些数组不是类型兼容的,可是,在 Java 中数组却是类型兼容的,这叫做协变数组类型。在 Java 中,每个数组都明了它所允许存储的对象类型,并且会在运行时做类型检查(这也是为什么不能创建泛型数组的原因,数组创建时必须知道确切类型)。如果像上面一样将一个不兼容的类型插入数组中,那么虚拟机将抛出一个 ArrayStoreException 异常。
数组记得它内部元素的具体类型,并且会在运行时做类型检查。这就是当初敢于把数组设计成协变的原因,这也是为什么容器 Collection 不能设计成协变的原因(Collection 不做运行时类型检查)。
一点历史
因为 JDK 1.5 之前还没有泛型,但很多代码迫切需要泛型来解决问题,就只能使用协变数组解决。这是对早期没有泛型的妥协,《Effective Java》中明确地指出,Java 允许数组协变是 Java 的缺陷。