Java 泛型通配符
# 假设
class Food {}
class Fruit extends Food {}
class Apple extends Fruit {}
class Orange extends Fruit{}
# ? extends class
extends 很直接明了,继承。这句话含义:某个类继承了 class。例如 ? extends Fruit,意思是某个类继承了 Fruit,可以是 Orange 等等。
插入数据:完全不可以。当类型擦除时,编译器只能知道他是某个继承了 class 的类,并不能确定是什么类,插入数据容易引起类型错误。
可以举个例子,现在有一个方法,public void add(ArrayList<? extends Fruit> fruit) {fruit.add(new Apple());},暂时不对判断对错,先分析。
假设,ArrayList 指向的是 ArrayList<Apple>,那没问题;假设指向的是 ArrayList<Orange>,那显然类型转换错误。
Object 也不能插入,Object 并没有继承 Fruit。
所以为了类型安全起见,必须让 fruit.add(new Apple()) 产生编译错误。
读取数据:完全可以。既然是继承了 class 的类,必然可以将其中元素向上转型成 class 取出。
可以举个例子,现在有一个方法,public void get(ArrayList<? extends Fruit> fruit) {fruit.get();},暂时不对判断对错,先分析。
假设 ArrayList 指向的是 ArrayList<Apple>,将取出来的 Apple 向上转型为 Fruit;假设指向的是 ArrayList<Orange>,将取出来的 Orange 向上转型为 Fruit。
故可行。
# ? super class
super 很直接明了,被继承。这句话含义:某个类被 class 继承。例如 ? super Fruit,意思是某个类被 Fruit 继承,可以是 Food 等等。
插入数据:部分可以。当类型擦除时,编译器可以知道它至多是类 class,可以将继承 class 的类向上转型之后插入,而非继承 class 的类,向下转型不能保证成功,所以不能加入。
可以举个例子,现在有一个方法,public void add(ArrayList<? super Fruit> fruit) {fruit.add(new Apple())},暂时不对判断对错,先分析。
这个 ArrayList 插入 Apple,那是没问题的,不过这个 Apple 要向上转型;那如果指向的是 ArrayList<Orange> 呢?也没有问题,把 Orange 向上转型,反正 ArrayList<? super Fruit> 顶层就是一定有 ? extend Fruit 这个类了。
故可行。
Objecet也不能插入。准确的来说,被 class 继承的类,都不能插入。
可以举个例子,现在有一个方法,public void add(ArrayList<? super Fruit> fruit) {fruit.add(new Object())},暂时不对判断对错,先分析。
这个 ArrayList 实际指向的是 ArrayList<Object>,那是没问题的,不过这个 Apple 要向上转型;那如果指向的是ArrayList<Food>呢?Object 不是 Food,无法将插入的 Object 转换为 Food。
所以为了类型安全起见,必须让 fruit.add(new Object()) 产生编译错误。
读取数据:很别扭,但是可以。编译器可以知道它至多是类 class,但并不能知晓是 class 的哪一个父类,所以只能转换为最基础的父类——Object。
可以举个例子,现在有一个方法,public void get(ArrayList<? super Fruit> fruit) {fruit.get();},暂时不对判断对错,先分析。
假设 ArrayList 可能指向的是 ArrayList<Fruit>,也可能指向的是 ArrayList<Food>,最保险做法是返回 Object——这肯定没错。
故可行。
# 总结
作为一个开发者来说,Java 的泛型很糟糕,机制过于复杂。
作为构造 Java 的人来说,Java 的泛型很伟大,它保证了向后兼容性。
PECS原则:
- 如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends class 通配符(Producer Extends)
- 如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super class 通配符(Consumer Super)
- 如果既要存又要取,那么就不要使用任何通配符