从“白马非马”到“协变、逆变”
一段哲学辩论
据说,公孙龙有一次需要过关, 关吏只允许人通过,于是有了下面的辩论。
关吏:”按照惯例,过关人可以,但马不行。”
公孙龙:”我的是白马,不是马。”
关吏:”白马怎么能不是马呢?”
公孙龙:”‘马’是对物’形’方面的规定,’白马’则是对马’色’方面的规定,对’色’方面的规定与对’形’方面的规定,自然是不同的,所以说,对不同的概念加以不同规定的结果,白马与马也是不同的。”
关吏:”有了白马,就不可以说没有马。既然不可以说没有马,那么白马不就是马了么?既然有白马称为有马,那么为什么白色的马就不是马呢?”
公孙龙:”如果你要求得到’马’,黄马、黑马都可以满足要求;如果要求得到白马,黄马、黑马就不能满足要求了。假如白马就是马,那么要求得到马与要求得到白马便完全一样了,这就表明白马与马不一样,所以白马非马。”
…略 公孙龙成功过关…
在这个故事中,我们常人的理解,就如关吏一样,马是包含了白马、黄马、黑马……,所以我们认为白马是马
而公孙龙的角度则是,区分出白马的’形’与’色’,分析出他们在不同的场景下,不一样的性质。
协变与逆变
对象的继承关系,是面对对象设计思想的精髓所在,例如下列代码大家肯定不陌生,这表示了白马是马的子类。
1 |
|
这总自然“和谐”的关系转变,就称为协变。
在 java
中,数组是协变的,但会存在一些问题,如下列代码:
1 | Horse[] horses = new WhiteHorse[10]; |
而泛型是不变的,如:
1 | List<Horse> horseList = new ArrayList<Hourse>(); // 正常 |
extends 关键字
在 java
中,如果需要泛型实现协变,可以使用 extends 关键字:
1 | List<? extends Horse> horseList = new ArrayList<WhiteHorse>(); |
上述代码中,horseList
是协变的,白马的所有元素,都被认为是马,也就是白马是马的体现。
然而调用 add(new WhiteHorse())
却无法编译,又体现出了白马非马的特点。
原因在于 List<? extends Horse>
也可以向合法指向的 List<BlackHourse>
,显然里面放 WhiteHorse
是非法的,编译器不知道List<? extends Horse>
的所持有的具体类型是什么,所以一旦转型,就将丢失向其中消费对象的能力,而只保留了向外提供对象的能力。
在 kotlin
中,放弃了 java
的 extends
关键字的使用,而使用 out
更具语意的词汇,来表达协变的关系转换,只保留了向外提供对象的能力。
逆变与 super 关键字的回顾
与之对应的是,逆变,在 java
中,如果需要泛型实现逆变,可以使用 super
关键字:
1 | List<? super WhiteHorse> horseList = new ArrayList<Horse>(); |
上述代码中,horseList
是逆变的,List<? super WhiteHorse>
可以合法的指向所有的 WhiteHorse
父类的集合,显然里面放入 WhiteHorse
是正常的,因为所有能消费处理 WhiteHorse
父类的,肯定能消费处理 WhiteHorse
;但是向外提供的对象,则无法确定,只能退化到根类 Object
,也就是丢失了向外提供对象的能力,而只保留了消费对象的能力。
在 kotlin
中,放弃了 java
的 super
关键字的使用,而使用与 out
对应的 in
更具语意的词汇,来表达逆变的关系转换,只保留了消费对象的能力。
回顾白马非马
在公孙龙的辩论中,他的论点为,如果你要求得到’马’,黄马、黑马都可以满足要求;如果要求得到白马,黄马、黑马就不能满足要求了。假如白马就是马,那么要求得到马与要求得到白马便完全一样了,这就表明白马与马不一样,所以白马非马。
他正是站在了协变不能消费对象的角度,表达了白马非马的观点。