Por que precisamos de wilcard limitado? estende T> no método Collections.max ()
Eu li o impressionante "Effective Java" de Joshua Bloch. Mas um exemplo nos livros não ficou claro para mim. É retirado do capítulo sobre genéricos, o item exato é"Item 28: Use curingas limitados para aumentar a flexibilidade da API".
Neste item, é mostrado como escrever a versão mais universal e à prova de balas (no sistema de tipos de pontos de vista) do algoritmo de seleção de elemento máximo da coleção, usando parâmetros de tipo limitado e tipos curinga delimitados.
A assinatura final do método estático escrito é assim:
public static <T extends Comparable<? super T>> T max(List<? extends T> list)
E é basicamente o mesmo que o deCollections#max
função da biblioteca padrão.
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
Eu entendo por que precisamos de um caractere curingaT extends Comparable<? super T>
tipo restrição, mas é realmente necessário no tipo do argumento? Parece-me que será o mesmo se sairmos apenasList<T>
ouCollection<T>
não é? Eu quero dizer algo assim:
public static <T extends Comparable<? super T>> T wrongMin(Collection<T> xs)
Eu escrevi o seguinte exemplo tolo de usar ambas as assinaturas e não vejo nenhuma diferença:
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);
}
Então você pode sugerir um exemplo onde essa assinatura simplificada será inaceitável?
P.S. E por que diabos existeT extends Object
em implementação oficial?
Bem, graças a Bohemian eu consegui descobrir qual é a diferença entre eles.
Considere os dois métodos auxiliares a seguir
private static void expectsPointOrColoredPoint(Point p) {
System.out.println("Overloaded for Point");
}
private static void expectsPointOrColoredPoint(ColoredPoint p) {
System.out.println("Overloaded for ColoredPoint");
}
Claro, não é muito inteligente sobrecarregar o método tanto para a superclasse quanto para sua subclasse, mas nos permite ver que tipo de valor de retorno foi realmente inferido (points
éList<ColoredPoint>
como antes).
expectsPointOrColoredPoint(min(points)); // print "Overloaded for ColoredPoint"
expectsPointOrColoredPoint(wrongMin(points)); // print "Overloaded for ColoredPoint"
Para ambos os métodos, o tipo inferido foiColoredPoint
.
Às vezes você quer ser explícito sobre o tipo passado para a função sobrecarregada. Você pode fazer isso de duas maneiras:
Você pode conjurar:
expectsPointOrColoredPoint((Point) min(points)); // print "Overloaded for Point"
expectsPointOrColoredPoint((Point) wrongMin(points)); // print "Overloaded for Point"
Ainda não há diferença ...
Ou você pode dizer ao compilador qual tipo deve ser inferido usando a sintaxeclass.<type>method
:
expectsPointOrColoredPoint(Algorithms.<Point>min(points)); // print "Overloaded for Point"
expectsPointOrColoredPoint(Algorithms.<Point>wrongMin(points)); // will not compile
Aha! Aqui está a resposta.List<ColoredPoint>
não pode ser passado para a função esperandoCollection<Point>
porque os genéricos não são covariantes (ao contrário dos arrays), mas podem ser passados para a função esperandoCollection<? extends Point>
.
Eu não tenho certeza onde ou quem pode preferir usar o parâmetro de tipo explícito em tal caso, mas pelo menos ele mostra onde owrongMin
pode ser inadequado.
E graças a @erickson e @ tom-hawtin-tackline por respostas sobre o propósito deT extends Object
restrição.