关于Java协变性的思考

来源:互联网 发布:php麻将机器人ai算法 编辑:程序博客网 时间:2024/06/09 19:02

Java协变性

一、什么是协变性?

简而言之,如果A IS-A B,那么A[] IS-A B[]。
举例:现在有类型Person、Employee和Student。Employee 是一个(IS-A) Person,Student是一个(IS-A)Person。那么下面的语句可以通过编译:

    Person[] arr = new Employee[5];   //能通过编译,因为协变性,数组是兼容的    arr[0] = new Student();  //能通过编译,因为Student IS-A Person

但是上面的代码在运行时却会出错。因为arr[0]实际上是引用一个Employee,可是Student IS-NOT-A Employee。这样就产生了混乱。这种错误正是由于Java数组的协变性而产生的。那么Java为什么不禁止数组协变呢?因为SE5之前还没有泛型,但很多代码迫切需要泛型来解决问题。
例如Arrays.equals()方法的底层实现调用的是Object.equals()方法,和数组中元素的具体类型无关,这充分利用了Java中任何类型都继承自Object类的特性,避免了为每个类型都重新定义Arrays.equals()方法。而在没有泛型的时代,要让Object[]能接受所有数组类型,最简单的办法就是让数组接受协变,把String[],Integer[]都定义成Object[]的派生类,然后多态就起作用了。

二、为什么数组设计成”协变“不会有大问题呢?

这是基于数组的一个独有特性:

数组记得它内部元素的具体类型,并且会在运行时做类型检查。

因为arr[0]记得它内部的元素类型是Employee,所以运行时给它插入一个Student类型会报错。
这个特性使得Java数组协变带来的影响不会酿成大错——错误最终还是会被检测出来,只不过是从编译时推迟到了运行时。
正是有这个特性,Java当初才敢于把数组设计成协变的。虽然向上转型以后,编译期类型检查放松了,但因为数组运行时对内部元素类型的严格检查,不匹配的类型还是插不进去的。这也是为什么容器Collection不能设计成协变的原因——Collection不做运行时类型检查。
Java容器因为不具有协变性,因而少了灵活性。因此,在Java5中,Java通过通配符和泛型对容器进行了弥补。

原创粉丝点击