类型协变和逆变,什么鬼?干什么的?怎么用?
不少童鞋面试的时候会被问到关于类型协变和逆变的问题。这不是最近才发生的事情,自从Java5.0开始引入类型协变和逆变的概念到现在,无数的无辜童鞋面试折在这个上面。虽然不是什么新概念、新技术,但是真TMD绕啊!
下面我们就来看看类型协变和逆变到底是什么鬼。
首先,既然是“变”,那么大家就不要觉得所谓“协变”和“逆变”是类型系统自然的结论,一定有什么不那么自然的事情才够刺激!
那么先看看最自然的是什么情况(没有"变"哦):
class Fruit {}
class Apple extends Fruit { ... }
class Orange extends Fruit { ... }
大家一定认可这样的代码:
Fruit fruit = null;
Apple apple = new Apple();
fruit = apple;
那下面的代码呢?
Fruit[] fruitAry = null;
Apple[] appleAry = new Apple[10];
fruitAry = appleAry;
这个看起来很自然啊!我说我要一个能装水果的篮子,然后你给我一个装苹果的篮子,我说这就是我要的。多么自然啊!
但是不好意思,编译器不这么看。在编译器看来,它只知道苹果是一种水果,现在你拿着一个装苹果的篮子,硬说它是装水果的篮子,编译器当然不同意了。结果就是“编译出错!”
这种情况是出在5.0以前的编译器上的。那么现在的编译器又是怎么说的呢?
现在的编译器把一个具有泛型参数T的泛型类型看做是关于类型T的一种变换,类型T的数组也可以看做是接受泛型参数为T的一种泛型类型。注意这里仅仅是看做,是理论上的,就是说从理论上String[]和Array<String>是等价的。
所以后面的讨论,都是基于泛型来进行的,不特别针对数组这种特殊情况了。
下面来看两个泛型类型的声明:
class Picker<T> {
public T pick() {
// ...
return (T)null;
}
}
class Ch