java - 为什么我们需要有界通配符 <?在 Collections.max() 方法中扩展 T>

标签 java generics effective-java

我读过 Joshua Bloch 写的很棒的“Effective Java”。但是我不清楚书中的一个例子。它摘自关于泛型的章节,确切的条目是“第 28 条:使用有界通配符来增加 API 灵 active ”

在本项目中,它展示了如何使用有界类型参数和有界通配符类型编写最通用和防弹(从类型系统的角度来看)版本的从集合中选择最大元素的算法。

写的静态方法的最终签名是这样的:

public static <T extends Comparable<? super T>> T max(List<? extends T> list)

它与 Collections#max 中的一个基本相同来自标准库的函数。

public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) 

我理解为什么我们需要在 T extends Comparable<? super T> 中使用有界通配符类型约束,但在参数类型中真的有必要吗?在我看来,如果我们只留下 List<T> 也是一样的或 Collection<T> ,不是吗?我的意思是这样的:

public static <T extends Comparable<? super T>> T wrongMin(Collection<T> xs)

我编写了以下使用这两种签名的愚蠢示例,但没有发现任何区别:

public class Algorithms {
    public static class ColoredPoint extends Point {
        public final Color color;

        public ColoredPoint(int x, int y, Color color) {
            super(x, y);
            this.color = color;
        }
        @Override
        public String toString() {
            return String.format("ColoredPoint(x=%d, y=%d, color=%s)", x, y, color);
        }
    }

    public static class Point implements Comparable<Point> {
        public final int x, y;

        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
        @Override
        public String toString() {
            return String.format("Point(x=%d, y=%d)", x, y);
        }
        @Override
        public int compareTo(Point p) {
            return x != p.x ? x - p.x : y - p.y;
        }
    }

    public static <T extends Comparable<? super T>> T min(Collection<? extends T> xs) {
        Iterator<? extends T> iter = xs.iterator();
        if (!iter.hasNext()) {
            throw new IllegalArgumentException("Collection is empty");
        }
        T minElem = iter.next();
        while (iter.hasNext()) {
            T elem = iter.next();
            if (elem.compareTo(minElem) < 0) {
                minElem = elem;
            }
        }
        return minElem;
    }

    public static <T extends Comparable<? super T>> T wrongMin(Collection<T> xs) {
        return min(xs);
    }

    public static void main(String[] args) {
        List<ColoredPoint> points = Arrays.asList(
                new ColoredPoint(1, 2, Color.BLACK),
                new ColoredPoint(0, 2, Color.BLUE),
                new ColoredPoint(0, -1, Color.RED)
        );
        Point p1 = wrongMin(points);
        Point p2 = min(points);
        System.out.println("Minimum element is " + p1);
    }

那么您能举个例子说明这种简化的签名是 Not Acceptable 吗?

附言为什么会有T extends Object在正式实现中?

回答

好吧,多亏了@Bohemian,我才弄清楚它们之间的区别。

考虑以下两种辅助方法

private static void expectsPointOrColoredPoint(Point p) {
    System.out.println("Overloaded for Point");
}

private static void expectsPointOrColoredPoint(ColoredPoint p) {
    System.out.println("Overloaded for ColoredPoint");
}

当然,为父类(super class)及其子类重载方法不是很聪明,但它让我们看到实际推断出的返回值类型(points 和以前一样是 List<ColoredPoint>)。

expectsPointOrColoredPoint(min(points));     // print "Overloaded for ColoredPoint"
expectsPointOrColoredPoint(wrongMin(points)); // print "Overloaded for ColoredPoint"

对于这两种方法,推断类型都是 ColoredPoint .

有时您希望明确传递给重载函数的类型。您可以通过几种方式进行:

你可以投:

expectsPointOrColoredPoint((Point) min(points));     // print "Overloaded for Point"
expectsPointOrColoredPoint((Point) wrongMin(points)); // print "Overloaded for Point"

仍然没有区别...

或者您可以使用语法 class.<type>method 告诉编译器应该推断出什么类型:

expectsPointOrColoredPoint(Algorithms.<Point>min(points));     // print "Overloaded for Point"
expectsPointOrColoredPoint(Algorithms.<Point>wrongMin(points)); // will not compile

啊哈!这是答案。 List<ColoredPoint>无法传递给需要 Collection<Point> 的函数因为泛型不是协变的(不像数组),但可以传递给期望 Collection<? extends Point> 的函数.

在这种情况下,我不确定在哪里或谁可能更喜欢使用显式类型参数,但至少它显示了 wrongMin 在哪里可能不合适。

感谢@erickson 和@tom-hawtin-tackline 对 T extends Object 目的的回答约束条件。

最佳答案

不同之处在于返回的类型,尤其是受推理的影响,其中类型可能是 Comparable 类型和List 类型之间的分层类型。让我举个例子:

class Top {
}
class Middle extends Top implements Comparable<Top> {
    @Override
    public int compareTo(Top o) {
        // 
    }
}
class Bottom extends Middle {
}

使用您提供的签名:

public static <T extends Comparable<? super T>> T max(List<? extends T> list)

我们可以在没有错误、警告或(重要的)转换的情况下编写代码:

List<Bottom> list;
Middle max = max(list); // T inferred to be Middle

如果您需要 Middle 结果,无需推断,您可以显式键入对 Middle 的调用:

 Comparable<Top> max = MyClass.<Middle>max(list); // No cast

或传递给接受Middle 的方法(其中推理不起作用)

someGenericMethodThatExpectsGenericBoundedToMiddle(MyClass.<Middle>max(list));

我不知道这是否有帮助,但为了说明编译器允许/推断的类型,签名看起来像这样(当然不是编译):

public static <Middle extends Comparable<Top>> Middle max(List<Bottom> list)

关于java - 为什么我们需要有界通配符 <?在 Collections.max() 方法中扩展 T>,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47055975/

相关文章:

java - 在 onDestroy() 上清空 Fragment 的变量

c# - 对象到/从 XML : generic Load and Save

java - Joshua Bloch 在 Effective Java 中解释的枚举类型

java - 类管理自己的内存

java - 终结器,关闭文件和流

java - 2D ArrayList 索引越界

java - 如何用javapoet动态生成代码?更改方法参数

java - 在 googledriverestapijava 中插入文件时出现权限异常

generics - 哪种语言的泛型是 OCaml 中类似于 C++、Java 或 C# 的泛型类和函​​数?

Swift:与 where 约束关联的类型