Dlaczego potrzebujemy ograniczonej karty wilcard <? extends T> w kolekcji Collections.max ()
Czytałem niesamowitą „Skuteczną Javę” Joshua Blocha. Ale jeden przykład w książkach jest dla mnie niejasny. Został zaczerpnięty z rozdziału o lekach generycznych, dokładna pozycja to„Punkt 28: Użyj ograniczonych symboli wieloznacznych, aby zwiększyć elastyczność interfejsu API”.
W tym punkcie pokazano, jak napisać najbardziej uniwersalną i kuloodporną (w typie systemu punkt widzenia) wersję algorytmu wyboru elementu maksymalnego z kolekcji przy użyciu parametrów typu ograniczonego i ograniczonych typów wieloznacznych.
Ostateczna sygnatura napisanej metody statycznej wygląda następująco:
public static <T extends Comparable<? super T>> T max(List<? extends T> list)
I w większości jest taki sam jak wCollections#max
funkcja z biblioteki standardowej.
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
Rozumiem, dlaczego potrzebujemy ograniczonego znaku wieloznacznegoT extends Comparable<? super T>
ograniczenie typu, ale czy jest to naprawdę konieczne w typie argumentu? Wydaje mi się, że będzie tak samo, jeśli tylko odejdziemyList<T>
lubCollection<T>
, prawda? Mam na myśli coś takiego:
public static <T extends Comparable<? super T>> T wrongMin(Collection<T> xs)
Napisałem następujący głupi przykład używania obu podpisów i nie widzę żadnej różnicy:
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);
}
Czy możesz zaproponować przykład, w którym taki uproszczony podpis będzie niedopuszczalny?
P.S. I dlaczego do cholery jestT extends Object
w oficjalnej implementacji?
Cóż, dzięki @Bohemian udało mi się ustalić, jaka jest różnica między nimi.
Rozważ następujące dwie metody pomocnicze
private static void expectsPointOrColoredPoint(Point p) {
System.out.println("Overloaded for Point");
}
private static void expectsPointOrColoredPoint(ColoredPoint p) {
System.out.println("Overloaded for ColoredPoint");
}
Jasne, przeciążanie metody zarówno dla superklasy, jak i dla jej podklasy nie jest zbyt inteligentne, ale pozwala nam zobaczyć, jaki typ wartości zwracanej został faktycznie wywnioskowany (points
jestList<ColoredPoint>
jak wcześniej).
expectsPointOrColoredPoint(min(points)); // print "Overloaded for ColoredPoint"
expectsPointOrColoredPoint(wrongMin(points)); // print "Overloaded for ColoredPoint"
Dla obu metod wywnioskowano typColoredPoint
.
Czasami chcesz mieć wyraźne informacje o typie przekazywanym do funkcji przeciążonej. Możesz to zrobić na kilka sposobów:
Możesz rzucać:
expectsPointOrColoredPoint((Point) min(points)); // print "Overloaded for Point"
expectsPointOrColoredPoint((Point) wrongMin(points)); // print "Overloaded for Point"
Nadal nie ma różnicy ...
Lub możesz powiedzieć kompilatorowi, jaki typ należy wywnioskować za pomocą składniclass.<type>method
:
expectsPointOrColoredPoint(Algorithms.<Point>min(points)); // print "Overloaded for Point"
expectsPointOrColoredPoint(Algorithms.<Point>wrongMin(points)); // will not compile
Aha! Oto odpowiedź.List<ColoredPoint>
nie można przekazać do funkcji oczekiwanejCollection<Point>
ponieważ typy generyczne nie są kowariantne (w przeciwieństwie do tablic), ale mogą być przekazywane do funkcji oczekiwanejCollection<? extends Point>
.
Nie jestem pewien, gdzie lub kto może w takim przypadku użyć jawnego parametru typu, ale przynajmniej pokazuje, gdziewrongMin
może być niewłaściwe.
Dzięki @erickson i @ tom-hawtin-tackline za odpowiedzi na temat celuT extends Object
przymus.