在 Java 中,协变允许 API 设计者指定一个实例可以泛化为某种类型或该类型的任何子类型。例如:
List<? extends Shape> shapes = new ArrayList<Circle>();
// where type Circle extends Shape
逆变则相反。它允许我们指定一个实例可以泛化为某种类型或超类型。
List<? super Shape> shapes = new ArrayList<Geometry>();
// where Shape extends Geometry
Java 泛型的逆变有何用处?您什么时候会选择使用它?
这是来自 Java 泛型和集合的相关摘录:
尽可能插入通配符可能是一个好习惯,但是您如何决定 使用哪个通配符?哪里应该使用
extends
,哪里应该使用super
,
哪里不适合使用通配符?
幸运的是,一个简单的原则就可以决定哪一个是合适的。
获取和放置原则:使用
通配符,当你只得到 结构体中的值,使用extends
当您仅将值放入时通配符 结构体,并且不使用通配符 当你既获取又放置时。super
我们已经在复制方法的签名中看到了这一原则的作用:
public static <T> void copy(List<? super T> dest, List<? extends T> src)
该方法从源 src 中获取值,因此使用
extends
通配符声明,
并将值放入目标 dst,因此使用 super
通配符声明。
每当您使用迭代器时,您都会从结构中获取值,因此请使用 extends
通配符。这是一个方法,它接受数字集合,将每个数字转换为双精度值,
并总结它们:
public static double sum(Collection<? extends Number> nums) {
double s = 0.0;
for (Number num : nums) s += num.doubleValue();
return s;
}
好吧,你的第二个例子可以让你写:
Shape shape = getShapeFromSomewhere();
shapes.add(shape);
而第一种形式则无法做到这一点。我承认,它不像协方差那么有用。
它可以有用的一个领域是比较。例如,考虑:
class AreaComparer implements Comparator<Shape>
...
您可以使用它来比较任何两个形状...因此,如果我们能够也使用它来对
List<Circle>
进行排序,那就太好了。幸运的是,我们可以通过逆变来做到这一点,这就是为什么 Collections.sort
存在过载:
public static <T> void sort(List<T> list, Comparator<? super T> c)
例如,在实现 Collections.addAll() 方法时,您需要一个可以包含某种类型 T 或 T 的超类型的集合。该方法如下所示:
public static <T> void addAll(Collection<? super T> collection, T... objects) {
// Do something
}