文章为平时学习记录所用,多些一遍加深记忆,方便复习。
面试官给写了一道题:
1. List<? extends Object> list = new ArrayList<>();
list.add("123");
2. List<? super String> list = new ArrayList<>();
list.add("123");
上面哪种写法是错误的?
内心OS:哈哈,这个我在《Java编程思想》上看过。答到:第一种写法是错的。
面试官又问:为啥?一个类型继承自Object,添加一个String类型进去是合理的啊。
我:....想不出来理由,好像第二个是错的?..
面试结果可想而知...
带着这个问题,我们来看一下java通配符
1. <? extends Class>
例子:List<? extends Object> = new ArrayList<>();
可以读作“具有任何从Object继承的类型的列表”,但这并不意味着这个列表可以持有任意类型的Object。通配符引用的是明确的类型。通配符引用的是明确的类型。通配符引用的是明确的类型。重要的话重复三遍,通配符代表列表的类型是Object的一个子类,但是它必须是一个明确的具体类型,比如String,比如Integer,但不能二者皆是。所以,当还不确定list里的具体类型是什么的时候,你不能向列表中加入元素。加入了一个String,它的具体类型是Integer咋办?
那么当我们明确了通配符的类型时:
List<? extends Object> list = new ArrayList<String>();
我们就能向其中加入元素了吗,这样看起来可以加入String类型及其子类型。
但是...还是不行,即使明确了列表的具体类型,还是不能向其中添加元素,因为编译器不知道。。(很想打编译器)
这种通配符的使用,只能在初始化时,向其中加入元素:
List<? extends Object> list = Arrays.aslist(new String("123"));
说到这里,已经明白了开头的问题到底是为啥。
继续来看一下其他的通配符
2. 逆变。超类型通配符<? super Class>
List<? super Object> list = new ArrayList<>();
可以读作“任何是Object超类类型的列表”,这个写法其实有点奇怪,Object是继承体系中的根类,没有继承自任何类。<? super Object>同样引用的是明确的类型,并不是变化的类型。而这个“具体的类型”继承自Object,那么向其中添加Object及Object的子类都应该是可以的。的确是这样,这个列表其实可以加入任何类型。。
通配符的使用准则PECS(producer-extends, consumer-super):
生产者使用<? extends Class>,只能取,提供数据,生成类型一般固定为几个特定的类型,可以在初始化时加入。
消费者使用<? super Class>,生产者生产的数据,消费者需要put进数据结构后使用。
3.无界通配符<?>
无界通配符<?>看起来意味着“任何事物”,因为使用无界通配符好像等价于使用原生类型。<?>可以被认为是一种装饰,它是在声明:我想用Java泛型来写代码。
无界通配符的另一个作用是:当你在处理多个泛型参数时,有时允许一个参数可以是任何类型,同时为其他参数确定某种特定类型。这时候无界通配符就显得很重要。
使用无界通配符时,与通配符<? extends Class>有一点是一样的,就是不能向其中加入元素。
List<?> list;
list.add(new Object())是不合法的,因为无界通配符<?>也是引用的是明确的类型,而不知道具体的类型是什么,所以不允许向列表中加入元素。那么无界通配符<?>也只能作为生产者使用。
4. 协变
上面讲到了逆变,这里再来谈一谈协变性质。
class Fruit{}
class Apple extends Fruit{}
数组的协变:Fruit[] f = new Apple[10];
但是泛型容器是不具有协变性质的:
List<Apple> apples = new ArrayList<>();
List<Fruit> list = apples; // error: incompatible types
即使Apple是Fruit的子类,也不能将Apple的列表赋值给Fruit的列表。因为Apple的列表,不是Fruit的列表。
参考书籍:《Java编程思想》