Зачем нам нужна ограниченная wilcard <? расширяет метод T> в Collections.max()

Я прочитал потрясающую "Эффективную Яву" Джошуа Блоха. Но один пример в книгах мне остается неясным. Это взято из главы о дженериках, точный элемент . Пункт 28: Используйте ограниченные групповые символы для повышения гибкости API..

В этом пункте показано, как написать наиболее универсальную и пуленепробиваемую (с точки зрения типа системы) версию алгоритма выбора максимального элемента из коллекции с использованием параметров ограниченного типа и ограниченных типов подстановок.

Окончательная подпись написанного статического метода выглядит так:

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);
    }

Итак, можете ли вы предложить пример, когда такая упрощенная подпись будет неприемлема?

P.S. И почему черт в официальной реализации 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");
}

Конечно, очень сложно перегружать метод как для суперкласса, так и для его подкласса, но он позволяет нам увидеть, какой тип возвращаемого значения был фактически выведен (points is 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>, потому что generics не являются ковариантными (в отличие от массивов), но могут быть переданы функции, ожидающей Collection<? extends Point>.

Я не уверен, где или кто может предпочесть использовать явный параметр типа в таком случае, но по крайней мере он показывает, где wrongMin может быть неуместным.

И благодаря @erickson и @tom-hawtin-tackline для ответов о цели ограничения T extends Object.

Ответы

Ответ 1

Разница заключается в возвращаемом типе, особенно под влиянием вывода, в котором тип может быть типом иерархически между классом 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)

Ответ 2

Разница между

T max(Collection<? extends T> coll)

и

T wrongMax(Collection<T> xs)

заключается в том, что возвращаемый тип второй версии точно такой же, как тип элемента коллекции T, тогда как в первой версии T может быть супер тип типа элемента.

Второй вопрос: причина T extends Object гарантирует, что T - это класс, а не интерфейс.


Обновление: Немного более "естественная" демонстрация разницы: предположим, вы определяете эти два метода:

static void happy(ColoredPoint p, Point q) {}
static void happy(Point p, ColoredPoint q) {}

И вызовите первый из них следующим образом:

happy(coloredPoint, min(points));
happy(coloredPoint, wrongMin(points));

Механизм вывода типа мог бы вывести, что в первом вызове тип возврата min должен быть Point, и код будет компилироваться. Второй вызов не скомпилировался, поскольку вызов happy неоднозначен.

К сожалению, механизм вывода типа не достаточно мощный, по крайней мере, в Java 7, поэтому на самом деле оба вызова не скомпилируются. Разница в том, что первый вызов можно зафиксировать, указав параметр типа, как в Algorithms.<Point>min, тогда как для фиксации второго вызова потребуется явный листинг.

Ответ 3

Нелегкий, но я постараюсь быть как можно более конкретным:

в T max(Collection<? extends T> coll) вы можете передать такой аргумент List<Animal> or List<Cat> or List<Dog>, и в T wrongMax(Collection<T> xs) где T - Animal, вы не можете пройти как аргумент this List<Dog>, List<Cat> Конечно, в Runtime вы могли бы добавить объекты Cat или Dog в List<Animal>, но во время компиляции вы не смогли бы передать подкласс Animal в Тип списка, передаваемый в качестве аргумента в wrongMax метод, с другой стороны, в методе max, который вы могли бы использовать. Извините за мой английский, я все еще его изучаю:), С уважением.