Зачем нам нужна ограниченная 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
, который вы могли бы использовать. Извините за мой английский, я все еще его изучаю:), С уважением.