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

时间:2010-10-05 05:54:00

标签: java generics contravariance

在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 generic的逆变是如何有用的?你什么时候选择使用它?

3 个答案:

答案 0 :(得分:34)

以下是Java Generics and Collections的相关摘录:

2.4。获取和放置原则

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

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

  

获取和放置原则:使用   当你得到时,extends通配符   结构中的值,使用super   只将值放入时的通配符   一个结构,不要使用通配符   当你们都得到并放好。

我们已经在复制方法的签名中看到了这个原则:

public static <T> void copy(List<? super T> dest, List<? extends T> src)

该方法从源src中获取值,因此使用extends通配符声明它, 它将值放入目标dst,因此使用super通配符声明它。 每当使用迭代器时,都会从结构中获取值,因此请使用extends 通配符。这是一个采用数字集合的方法,将每个数字转换为double, 总结一下:

public static double sum(Collection<? extends Number> nums) {
    double s = 0.0;
    for (Number num : nums) s += num.doubleValue();
    return s;
}

答案 1 :(得分:30)

嗯,你的第二个例子允许你写:

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)

答案 2 :(得分:6)

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

public static <T> void addAll(Collection<? super T> collection, T... objects) {
    // Do something
}