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

eas*_*825 17 java generics effective-java

我读过Joshua Bloch的精彩"有效Java".但书中的一个例子对我来说还不清楚.它来自关于泛型的章节,具体项目是"第28项:使用有界通配符来增加API灵活性".

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

编写的静态方法的最终签名如下所示:

public static <T extends Comparable<? super T>> T max(List<? extends T> list)
Run Code Online (Sandbox Code Playgroud)

Collections#max与标准库中的函数大致相同.

public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) 
Run Code Online (Sandbox Code Playgroud)

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

public static <T extends Comparable<? super T>> T wrongMin(Collection<T> xs)
Run Code Online (Sandbox Code Playgroud)

我写了以下使用两个签名的愚蠢示例,并没有看到任何不同之处:

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);
    }
Run Code Online (Sandbox Code Playgroud)

那么你可以建议一个这样简化的签名是不可接受的例子吗?

PS为什么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");
}
Run Code Online (Sandbox Code Playgroud)

当然,这不是很聪明重载方法既为超和它的子类,但它让我们看到什么类型的返回值实际上是推断(pointsList<ColoredPoint>如前).

expectsPointOrColoredPoint(min(points));     // print "Overloaded for ColoredPoint"
expectsPointOrColoredPoint(wrongMin(points)); // print "Overloaded for ColoredPoint"
Run Code Online (Sandbox Code Playgroud)

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

有时您希望明确传递给重载函数的类型.您可以通过以下几种方式完成此操作:

你可以施放:

expectsPointOrColoredPoint((Point) min(points));     // print "Overloaded for Point"
expectsPointOrColoredPoint((Point) wrongMin(points)); // print "Overloaded for Point"
Run Code Online (Sandbox Code Playgroud)

仍然没有区别......

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

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

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

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

感谢@erickson和@ tom-hawtin-tackline关于T extends Object约束目的的答案.

Boh*_*ian 5

不同之处在于返回的类型,特别是受推理的影响,因此类型可以是Comparable类型和List类型之间的层次类型.让我举个例子:

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

使用您提供的签名:

public static <T extends Comparable<? super T>> T max(List<? extends T> list)
Run Code Online (Sandbox Code Playgroud)

我们可以编写这个没有错误,警告或(重要)演员:

List<Bottom> list;
Middle max = max(list); // T inferred to be Middle
Run Code Online (Sandbox Code Playgroud)

如果你需要一个Middle结果,没有推理,可以明确地输入呼叫Middle:

 Comparable<Top> max = MyClass.<Middle>max(list); // No cast
Run Code Online (Sandbox Code Playgroud)

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

someGenericMethodThatExpectsGenericBoundedToMiddle(MyClass.<Middle>max(list));
Run Code Online (Sandbox Code Playgroud)

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

public static <Middle extends Comparable<Top>> Middle max(List<Bottom> list)
Run Code Online (Sandbox Code Playgroud)