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

2022-09-01 12:30:29

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


答案 1

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

2.4. 获取和放置原则

尽可能插入通配符可能是一种很好的做法,但是如何决定使用哪个通配符呢?你应该在哪里使用,你应该在哪里使用,在哪里使用通配符是不合适的?extendssuper

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

获取和放置原则:仅从结构中获取值时使用通配符,仅将值放入结构时使用通配符,在获取和放置时不使用通配符。extendssuper

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

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

该方法从源 src 中获取值,因此使用通配符声明,并将值放入目标 dst 中,因此使用通配符声明。无论何时使用迭代器,都会从结构中获取值,因此请使用通配符。下面是一个方法,它采用一个数字集合,将每个数字转换为双精度值,然后对它们求和:extendssuperextends

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

答案 2

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

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)

推荐