Я прочитал потрясающую книгу Джошуа Блоха «Эффективная Java». Но один пример в книгах остался для меня неясным. Это взято из главы о дженериках, точный пункт - "Пункт 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
равно 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
.
T extends Object & ...
приводит к тому, что возвращаемый тип метода после стирания будетObject
вместоComparable
. Это было необходимо для сохранения сигнатуры метода, чтобы код, скомпилированный для старого API, продолжал работать. - person erickson   schedule 23.06.2013