问题描述
今天遇到了一个比较有意思的问题,如下
public class Main{
public static void main(String[] args) {
List<? extends C> list = new ArrayList<>();
list.add(new A()); // 编译报错
list.add(new B()); // 编译报错
list.add(new C()); // 编译报错
}
}
class C {}
class B extends C {}
class A extends B {}
添加元素的那三行代码,全部都编译报错了,很明显,这种行为被编译器阻止了,然后我试了一下,发现除了null,任何类型的数据全都无法添加。如果 List 的泛型指定为C的话,就不会报错了。
这里 List 的泛型是用通配符的形式 ? extends C ,指这个类型是 C 类或者 C 的子类,那么 A,B,C 都是 C 类或者 C 的子类,为什么无法添加?
问题解析
List<? extends T>
经过的搜索大法之后。结论就是 List<? extends T> 这种泛型的列表,只能读取,不能插入!!
例如:List<? extends Number>
这个 List 是什么类型的?这个问题是没有办法回答的。它可能是 List<Integer>,也可能是 List<Double>,实际上这个时候 List 的类型是还没有确定的,那么这个时候你能插入数据吗?我们不能插入一个Integer,因为这个列表有可能是 List<Double> ,也不能插入一个 Double,因为这个列表也可能是 List<Integer> ,因为我们不能保证列表实际指向的类型是什么,即不能保证列表中实际存储什么类型的对象。
但是有一点是可以保证的,就是这个列表指向的类型一定是 Number 类或者 Number 的子类,所以我们可以保证的是我们读取到的对象一定是 Number 类或者 Number 的子类,都可以向上转型为其父类 Number 类,所以我们可以保证读取到的元素类型一定是 Number 类。
List<? super T>
在通配符的泛型中和 extends 相对应的就是 super 。现在考虑一下 List<? super Number>
读取的时候:我们不能保证读取到的元素类型是 Number ,因为列表实际指向的类型有可能是 List<Object>,而如果没有经过一次向上转型,Object 无法向下转型为 Number。如果列表实际指向的类型是 Object ,那么是可以插入任何类型的元素,比如我插入了一个 Number 类型的元素和一个自定义的类 Agent 类,那么此时就无法保证获取到的元素类型是什么,也就没法用一个类型去接收,只有一种方法那就是用 Obejct 类型去接收数据,所以结论是 List<? super Number> 除了用 Object 类型接收,无法用其他类型去接收读取到的元素。
插入的时候:因为 List<? super Number> 是指向的类型一定是 Number 的父类,那么你可以插入一个 Number,因为 Number 会自动向上转型为它的父类。所以可以插入。
所以结论和上面相反,List<? super T> 这种泛型的列表,只能插入,以及只能用 Object 类读取!!
PECS原则
请记住PECS原则:生产者(Producer)使用 extends,消费者(Consumer)使用 super。
生产者使用 extends,如果你需要一个列表提供T类型的元素(即你想从列表中读取T类型的元素),你需要把这个列表声明成 <? extends T>,比如 List<? extends Integer>,因此你不能往该列表中添加任何元素。
消费者使用 super,如果需要一个列表使用T类型的元素(即你想把T类型的元素加入到列表中),你需要把这个列表声明成 <? super T>,比如 List<? super Integer>,因此你不能保证从中读取到的元素的类型。
即是生产者,也是消费者,如果一个列表即要生产,又要消费,你不能使用泛型通配符声明列表,比如List<Integer>
OK,涨知识了~