28

在 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 泛型的逆变性有何用处?你会选择什么时候使用它?

4

3 回答 3

36

以下是Java 泛型和集合的相关摘录:

2.4. 获取和放置原则

尽可能插入通配符可能是一种好习惯,但是您如何决定使用哪个通配符?你应该在哪里使用extends,你应该在哪里使用super,以及在哪里使用通配符根本不合适?

幸运的是,一个简单的原则决定了哪个是合适的。

Get and Put 原则extends当你只从结构中获取值时使用super 通配符,当你只将值放入结构时使用通配符,当你同时获取和放置时不要使用通配符。

我们已经在 copy 方法的签名中看到了这个原理:

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;
}
于 2010-10-05T12:10:02.287 回答
34

好吧,您的第二个示例将允许您编写:

Shape shape = getShapeFromSomewhere();
shapes.add(shape);

而你不能用第一种形式做到这一点。我承认,它不如协方差有用。

它可能有用的一个领域是比较。例如,考虑:

class AreaComparer implements Comparator<Shape>
...

您可以使用它来比较任何两个形状......因此,如果我们也可以使用它来对 a 进行排序,那就太好了List<Circle>。幸运的是,我们可以通过逆变来做到这一点,这就是为什么有 for 的重载Collections.sort

public static <T> void sort(List<T> list, Comparator<? super T> c)
于 2010-10-05T05:58:24.700 回答
8

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

public static <T> void addAll(Collection<? super T> collection, T... objects) {
    // Do something
}
于 2010-10-05T06:02:41.640 回答