一段哲学辩论

据说,公孙龙有一次需要过关, 关吏只允许人通过,于是有了下面的辩论。

关吏:”按照惯例,过关人可以,但马不行。”

公孙龙:”我的是白马,不是马。”

关吏:”白马怎么能不是马呢?”

公孙龙:”‘马’是对物’形’方面的规定,’白马’则是对马’色’方面的规定,对’色’方面的规定与对’形’方面的规定,自然是不同的,所以说,对不同的概念加以不同规定的结果,白马与马也是不同的。”

关吏:”有了白马,就不可以说没有马。既然不可以说没有马,那么白马不就是马了么?既然有白马称为有马,那么为什么白色的马就不是马呢?”

公孙龙:”如果你要求得到’马’,黄马、黑马都可以满足要求;如果要求得到白马,黄马、黑马就不能满足要求了。假如白马就是马,那么要求得到马与要求得到白马便完全一样了,这就表明白马与马不一样,所以白马非马。”

…略 公孙龙成功过关…

在这个故事中,我们常人的理解,就如关吏一样,马是包含了白马、黄马、黑马……,所以我们认为白马是马

而公孙龙的角度则是,区分出白马的’形’与’色’,分析出他们在不同的场景下,不一样的性质。

HorseWhiteHorseBlackHourseYellowHorse

协变与逆变

对象的继承关系,是面对对象设计思想的精髓所在,例如下列代码大家肯定不陌生,这表示了白马是马的子类。

1
2
3
4
5
6
7
8
9

class Horse {}

class WhiteHorse extends Horse {}
class YellowHorse extends Horse {}
class BlackHourse extends Horse {}

Horse horse = new WhiteHorse();

这总自然“和谐”的关系转变,就称为协变。

java 中,数组是协变的,但会存在一些问题,如下列代码:

1
2
3
Horse[] horses = new WhiteHorse[10]; 
horses[0] = new WhiteHorse();
horses[1] = new BlackHourse(); // 编译通过,运行时报错

而泛型是不变的,如:

1
2
List<Horse> horseList = new ArrayList<Hourse>(); // 正常
List<Horse> horseList = new ArrayList<WhiteHorse>(); // 编译报错

extends 关键字

java 中,如果需要泛型实现协变,可以使用 extends 关键字:

1
2
3
4
5
List<? extends Horse> horseList = new ArrayList<WhiteHorse>();

Horse horse = horseList.get(0); // 白马是马 (运行会报数组越界)

horseList.add(new WhiteHorse()); // 白马非马 编译报错

上述代码中,horseList 是协变的,白马的所有元素,都被认为是马,也就是白马是马的体现。

然而调用 add(new WhiteHorse()) 却无法编译,又体现出了白马非马的特点。

原因在于 List<? extends Horse> 也可以向合法指向的 List<BlackHourse>,显然里面放 WhiteHorse 是非法的,编译器不知道List<? extends Horse>的所持有的具体类型是什么,所以一旦转型,就将丢失向其中消费对象的能力,而只保留了向外提供对象的能力。

kotlin 中,放弃了 javaextends 关键字的使用,而使用 out 更具语意的词汇,来表达协变的关系转换,只保留了向外提供对象的能力。

List? extends HorseListWhiteHorseListBlackHourseListYellowHorse

逆变与 super 关键字的回顾

与之对应的是,逆变,在 java 中,如果需要泛型实现逆变,可以使用 super 关键字:

1
2
3
4
5
6
7
List<? super WhiteHorse> horseList = new ArrayList<Horse>();

horseList.add(new WhiteHorse());

WhiteHorse horse = horseList.get(0); // 编译报错,类型无法转换
Horse horse = horseList.get(0); // 编译报错,类型无法转换
Object object= horseList.get(0); // 编译通过

上述代码中,horseList 是逆变的,List<? super WhiteHorse> 可以合法的指向所有的 WhiteHorse 父类的集合,显然里面放入 WhiteHorse 是正常的,因为所有能消费处理 WhiteHorse 父类的,肯定能消费处理 WhiteHorse;但是向外提供的对象,则无法确定,只能退化到根类 Object,也就是丢失了向外提供对象的能力,而只保留了消费对象的能力。

kotlin 中,放弃了 javasuper 关键字的使用,而使用与 out 对应的 in 更具语意的词汇,来表达逆变的关系转换,只保留了消费对象的能力。

ListObjectListHorseList? super WhiteHorse

回顾白马非马

在公孙龙的辩论中,他的论点为,如果你要求得到’马’,黄马、黑马都可以满足要求;如果要求得到白马,黄马、黑马就不能满足要求了。假如白马就是马,那么要求得到马与要求得到白马便完全一样了,这就表明白马与马不一样,所以白马非马。

他正是站在了协变不能消费对象的角度,表达了白马非马的观点。

扩展阅读