在 Java 泛型中如何使用逆变?

问题描述 投票:0回答:3

在 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 generics contravariance
3个回答
36
投票

这是来自 Java 泛型和集合的相关摘录:

2.4。获取和放置原则

尽可能插入通配符可能是一个好习惯,但是您如何决定 使用哪个通配符?哪里应该使用

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;
}

34
投票

好吧,你的第二个例子可以让你写:

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)

8
投票

例如,在实现 Collections.addAll() 方法时,您需要一个可以包含某种类型 T 或 T 的超类型的集合。该方法如下所示:

public static <T> void addAll(Collection<? super T> collection, T... objects) {
    // Do something
}
© www.soinside.com 2019 - 2024. All rights reserved.