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?

Responda

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.

questionAnswers(3)

yourAnswerToTheQuestion