【问题标题】:How would contravariance be used in Java generics?Java泛型中如何使用逆变?
【发布时间】:2011-04-21 03:23:01
【问题描述】:

在 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


    【解决方案1】:

    这是Java Generics and Collections的相关摘录:

    2.4。获取和放置原则

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

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

    Get 和 Put 原则:使用 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;
    }
    

    【讨论】:

    • 这也被助记词“PECS”所熟知:Producer Extends,Consumer Super。 (网上很多解释。)
    • @JoshuaGoldberg 是的,但我在 2010 年不知道 :-)
    【解决方案2】:

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

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

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

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

    class AreaComparer implements Comparator<Shape>
    ...
    

    您可以使用它来比较任何两个形状...所以如果我们可以使用它来对List&lt;Circle&gt; 进行排序就好了。幸运的是,我们可以通过逆变来做到这一点,这就是为什么 Collections.sort 的重载:

    public static <T> void sort(List<T> list, Comparator<? super T> c)
    

    【讨论】:

    • 这是否意味着我可以调用 Collections.sort(shapes, new Comparator() { ... }); ?对象是 ,对吧?
    • @Synesso:是的,如果你有一个可以比较任意两个对象的比较器,那么它可以比较任意两个形状,所以它是安全的。
    • @JonSkeet: 仍然,我不明白为什么在第二种情况下我们需要明确地super。我知道使用extends 将无助于将任何形状添加到列表中,但List&lt;Shape&gt; shapes = new ArrayList&lt;Geometry&gt;();? 也同样有效,对吧?我发现 Oracle 的 documentation 在这种情况下也无法完全掌握 super 的用处 :-(
    • @zencv:关键是,如果你想编写一个方法,将一堆 Shape 元素添加到现有列表中,你不介意这是 List&lt;Shape&gt; 还是List&lt;Geometry&gt; - 例如,你可以有一个 List&lt;? super Shape&gt; listToAddTo 的方法参数。
    • @Joschua:这不是那个意思——它的意思是“它是某种类型的列表,并且该类型扩展了 Shape”。所以它可能是(比如说)List&lt;Square&gt;,而您不应该在其中添加Circle。 “包含 Shape 或从其扩展的元素的列表”只是 List&lt;Shape&gt;
    【解决方案3】:

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

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

    【讨论】:

    • 演示代码(无用的 IRL):List&lt;Animal&gt; l = List.of(); Collections.addAll(l, new Cow(), new Cow()); 多亏了逆变性,我们可以在动物列表中添加奶牛。
    猜你喜欢
    • 2021-10-24
    • 1970-01-01
    • 1970-01-01
    • 2013-11-29
    • 2011-12-27
    • 2021-01-22
    • 2021-12-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多