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

Syn*_*sso 25 java generics contravariance

在Java中,协方差允许API设计者指定实例可以被概括为某种类型或任何该类型的子类型.例如:

List<? extends Shape> shapes = new ArrayList<Circle>(); 
// where type Circle extends Shape
Run Code Online (Sandbox Code Playgroud)

反方差则是另一种方式.它允许我们指定实例可以概括为某种类型或超类型.

List<? super Shape> shapes = new ArrayList<Geometry>();
// where Shape extends Geometry
Run Code Online (Sandbox Code Playgroud)

Java泛型的逆变是如何有用的?你什么时候选择使用它?

Sea*_*oyd 35

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

2.4.获取和放置原则

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

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

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

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

public static <T> void copy(List<? super T> dest, List<? extends T> src)
Run Code Online (Sandbox Code Playgroud)

该方法从源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;
}
Run Code Online (Sandbox Code Playgroud)


Jon*_*eet 30

那么,你的第二个例子可以让你写:

Shape shape = getShapeFromSomewhere();
shapes.add(shape);
Run Code Online (Sandbox Code Playgroud)

而你不能用第一种形式做到这一点.它不像协方差那样有用,我会授予你.

比较有用的一个领域是比较.例如,考虑:

class AreaComparer implements Comparator<Shape>
...
Run Code Online (Sandbox Code Playgroud)

您可以使用它来比较任何两个形状......所以如果我们也可以使用它来排序List<Circle>例如,那将是很好的.幸运的是,我们可以通过逆转来做到这一点,这就是为什么有一个重载Collections.sort:

public static <T> void sort(List<T> list, Comparator<? super T> c)
Run Code Online (Sandbox Code Playgroud)

  • 这是否意味着我可以调用Collections.sort(形状,新的Comparator <Object>(){...}); ?对象是<?超级形状>,对吗? (2认同)

Mar*_*arc 7

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

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