Qual é a diferença entre <? super E> e <? estende E>?


147

Qual é a diferença entre <? super E>e <? extends E>?

Por exemplo, quando você analisa a classe, java.util.concurrent.LinkedBlockingQueuehá a seguinte assinatura para o construtor:

public LinkedBlockingQueue(Collection<? extends E> c)

e uma para o método:

public int drainTo(Collection<? super E> c)

Respostas:


182

O primeiro diz que é "algum tipo que é um ancestral de E"; o segundo diz que é "algum tipo que é uma subclasse de E". (Nos dois casos, o próprio E está bom.)

Portanto, o construtor usa o ? extends Eformulário para garantir que, quando buscar valores da coleção, todos eles serão E ou alguma subclasse (ou seja, é compatível). O drainTométodo está tentando colocar valores na coleção, portanto, a coleção precisa ter um tipo de elemento E ou uma superclasse .

Como exemplo, suponha que você tenha uma hierarquia de classes como esta:

Parent extends Object
Child extends Parent

e a LinkedBlockingQueue<Parent>. Você pode construir essa passagem de uma forma List<Child>que copiará todos os elementos com segurança, porque cada Childum é um pai. Não foi possível passar um List<Object>porque alguns elementos podem não ser compatíveis Parent.

Da mesma forma, você pode drenar essa fila para um List<Object>porque every Parenté um Object... mas você não pode drená-lo para um List<Child>porque List<Child>espera que todos os seus elementos sejam compatíveis Child.


25
+1. Essa é realmente a diferença prática. estende para buscar, super para inserir.
Yishai

1
@ Jon, o que você quer dizer com (nos dois casos, o próprio E está bom.) No primeiro parágrafo?
26713 Geek

2
@ Geek: Quero dizer que se você tem algo parecido ? extends InputStreamou ? super InputStreamentão você pode usar um InputStreamcomo argumento.
precisa

Eu realmente nunca recebi a explicação do PECS de Josh Block em Java eficaz. No entanto, @Yishai, essa é uma maneira útil de lembrar. Talvez possamos propor um novo mnemônico, SAGE: Super -> Adicionar / Obter -> Estender
dcompiled

Então, se eu ler direito, "<? Estende E>" requer que "?" é uma subclasse de "E" e "<? super E>" exige que "E" seja uma subclasse de "?", certo?
El Suscriptor Justiciero

130

As razões para isso são baseadas em como o Java implementa genéricos.

Um exemplo de matrizes

Com matrizes, você pode fazer isso (matrizes são covariantes)

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;

Mas, o que aconteceria se você tentar fazer isso?

myNumber[0] = 3.14; //attempt of heap pollution

Essa última linha seria compilada, mas se você executar esse código, poderá obter um ArrayStoreException. Porque você está tentando colocar um duplo em uma matriz inteira (independentemente de ser acessado por meio de uma referência numérica).

Isso significa que você pode enganar o compilador, mas não pode enganar o sistema de tipo de tempo de execução. E é assim porque matrizes são o que chamamos de tipos reificáveis . Isso significa que, em tempo de execução, Java sabe que essa matriz foi realmente instanciada como uma matriz de números inteiros que simplesmente é acessada por meio de uma referência do tipo Number[].

Então, como você pode ver, uma coisa é o tipo real do objeto e outra é o tipo de referência que você usa para acessá-lo, certo?

O problema com os genéricos Java

Agora, o problema com os tipos genéricos de Java é que as informações do tipo são descartadas pelo compilador e não estão disponíveis no tempo de execução. Esse processo é chamado de apagamento de tipo . Há boas razões para implementar genéricos como este em Java, mas isso é uma longa história, e tem a ver, entre outras coisas, com compatibilidade binária com código pré-existente (consulte Como obtemos os genéricos que temos ).

Mas o ponto importante aqui é que, já que, em tempo de execução, não há informações de tipo, não há como garantir que não estamos cometendo poluição de pilha.

Por exemplo,

List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);

List<Number> myNums = myInts; //compiler error
myNums.add(3.14); //heap pollution

Se o compilador Java não impedir que você faça isso, o sistema do tipo tempo de execução também não poderá impedi-lo, porque não há como, em tempo de execução, determinar que essa lista deveria ser apenas uma lista de números inteiros. O tempo de execução Java permite colocar o que você deseja nesta lista, quando deve conter apenas números inteiros, porque quando foi criado, foi declarado como uma lista de números inteiros.

Como tal, os designers de Java se certificaram de que você não pode enganar o compilador. Se você não pode enganar o compilador (como podemos fazer com matrizes), também não pode enganar o sistema de tipo de tempo de execução.

Como tal, dizemos que tipos genéricos não são reificáveis .

Evidentemente, isso dificultaria o polimorfismo. Considere o seguinte exemplo:

static long sum(Number[] numbers) {
   long summation = 0;
   for(Number number : numbers) {
      summation += number.longValue();
   }
   return summation;
}

Agora você pode usá-lo assim:

Integer[] myInts = {1,2,3,4,5};
Long[] myLongs = {1L, 2L, 3L, 4L, 5L};
Double[] myDoubles = {1.0, 2.0, 3.0, 4.0, 5.0};

System.out.println(sum(myInts));
System.out.println(sum(myLongs));
System.out.println(sum(myDoubles));

Mas se você tentar implementar o mesmo código com coleções genéricas, não terá êxito:

static long sum(List<Number> numbers) {
   long summation = 0;
   for(Number number : numbers) {
      summation += number.longValue();
   }
   return summation;
}

Você obteria erros de compilador se tentar ...

List<Integer> myInts = asList(1,2,3,4,5);
List<Long> myLongs = asList(1L, 2L, 3L, 4L, 5L);
List<Double> myDoubles = asList(1.0, 2.0, 3.0, 4.0, 5.0);

System.out.println(sum(myInts)); //compiler error
System.out.println(sum(myLongs)); //compiler error
System.out.println(sum(myDoubles)); //compiler error

A solução é aprender a usar dois recursos poderosos dos genéricos Java conhecidos como covariância e contravariância.

Covariância

Com a covariância, você pode ler itens de uma estrutura, mas não pode escrever nada nela. Todas estas são declarações válidas.

List<? extends Number> myNums = new ArrayList<Integer>();
List<? extends Number> myNums = new ArrayList<Float>();
List<? extends Number> myNums = new ArrayList<Double>();

E você pode ler em myNums:

Number n = myNums.get(0); 

Como você pode ter certeza de que, independentemente da lista real, ela pode ser convertida para um número (afinal, qualquer coisa que estenda o número é um número, certo?)

No entanto, você não tem permissão para colocar nada em uma estrutura covariante.

myNumst.add(45L); //compiler error

Isso não seria permitido, porque Java não pode garantir qual é o tipo real do objeto na estrutura genérica. Pode ser qualquer coisa que estenda Number, mas o compilador não pode ter certeza. Então você pode ler, mas não escrever.

Contravariância

Com contravariância, você pode fazer o oposto. Você pode colocar as coisas em uma estrutura genérica, mas não pode ler a partir dela.

List<Object> myObjs = new List<Object>();
myObjs.add("Luke");
myObjs.add("Obi-wan");

List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);

Nesse caso, a natureza real do objeto é uma Lista de objetos e, por contravariância, você pode colocar números nele, basicamente porque todos os números têm Objeto como seu ancestral comum. Como tal, todos os números são objetos e, portanto, isso é válido.

No entanto, você não pode ler com segurança nada dessa estrutura contravariante, assumindo que receberá um número.

Number myNum = myNums.get(0); //compiler-error

Como você pode ver, se o compilador permitir que você escreva essa linha, você obterá uma ClassCastException em tempo de execução.

Get / Put Princípio

Assim, use covariância quando pretender apenas retirar valores genéricos de uma estrutura, use contravariância quando pretender colocar valores genéricos em uma estrutura e use o tipo genérico exato quando pretender fazer as duas coisas.

O melhor exemplo que tenho é o seguinte, que copia qualquer tipo de número de uma lista para outra. Ele só obtém itens da fonte e apenas coloca itens no destino.

public static void copy(List<? extends Number> source, List<? super Number> target) {
    for(Number number : source) {
        target(number);
    }
}

Graças aos poderes de covariância e contravariância, isso funciona para um caso como este:

List<Integer> myInts = asList(1,2,3,4);
List<Double> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();

copy(myInts, myObjs);
copy(myDoubles, myObjs);

27
Esta resposta deve chegar ao topo. Boa explicação.
Suresh Atta

1
@edwindalorzo, há um pequeno erro de digitação que você deseja corrigir em Contravariance. Você diz List<Object> myObjs = new List<Object();(que está faltando o fechamento >do segundo Object).

Exemplos fantásticos, fáceis e claros desses conceitos sutis!
db1234

Algo que você pode adicionar para ajudar outras pessoas a se lembrarem das coisas. Quando você deseja chamar um método de uma superclasse, você usa super.methodName. Ao usar <? super E>, significa "algo na superdireção" em oposição a algo na extendsdireção. Exemplo: Objectestá na superdireção de Number(uma vez que é uma super classe) e Integerestá na extendsdireção (uma vez que se estende Number).
BrainStorm.exe

59

<? extends E>define Ecomo o limite superior: "Isso pode ser convertido em E".

<? super E>define Ecomo o limite inferior: " Epode ser convertido para isso".


6
Este é um dos melhores resumos simples / práticos da diferença que eu já vi.
JAB

1
Por décadas agora (com OOP) eu tenho lutado contra uma inversão instintiva das noções "superior" e "inferior". Agravante! Para mim, Objecté inerentemente uma classe de nível inferior, apesar de sua posição como a superclasse final (e desenhada verticalmente em UML ou em árvores de herança similares). Eu nunca fui capaz de desfazer isso apesar das eras de tentar.

3
@ tgm1024 "superclasse" e "subclasse" devem causar muitos problemas.
David Moles

@DavidMoles, por quê? Você claramente não está seguindo o que estou dizendo. "superclasse" é semelhante a "superconjunto"; a noção de especialização é uma redução na aplicabilidade no relacionamento IS-A. Apple é uma fruta. Frutas (superclasse) é um superconjunto, incluindo Apple (subclasse) como subconjunto. Essa relação verbal está bem. O que estou dizendo quebra é a noção de que "superior" e "inferior" têm mapeamentos intrínsecos para "superconjunto" e "subconjunto". Superior e Inferior devem ser evitados como termos em OO.

1
@ tgm1024 "Super-" vem do super latino "acima, em" e "sub-" do sub latino "abaixo, abaixo". Ou seja, etimologicamente, super está acima e sub está abaixo.
David Moles

12

Vou tentar responder isso. Mas, para obter uma resposta realmente boa, verifique o livro de Joshua Bloch, Effective Java (2nd Edition). Ele descreve o PECS mnemônico, que significa "Producer Extends, Consumer Super".

A idéia é que, se você codificar estiver consumindo os valores genéricos do objeto, deverá usar extensões. mas se você estiver produzindo novos valores para o tipo genérico, use super.

Então, por exemplo:

public void pushAll(Iterable<? extends E> src) {
  for (E e: src) 
    push(e);
}

E

public void popAll(Collection<? super E> dst) {
  while (!isEmpty())
    dst.add(pop())
}

Mas realmente você deve conferir este livro: http://java.sun.com/docs/books/effective/


12

<? super E> significa any object including E that is parent of E

<? extends E> significa any object including E that is child of E .


resposta curta e doce.
chirag soni 19/01

7

Você pode pesquisar no Google os termos contravariância ( <? super E>) e covariância ( <? extends E>). Descobri que a coisa mais útil ao compreender os genéricos era entender a assinatura do método Collection.addAll:

public interface Collection<T> {
    public boolean addAll(Collection<? extends T> c);
}

Assim como você gostaria de adicionar um Stringa List<Object>:

List<Object> lo = ...
lo.add("Hello")

Você também deve poder adicionar uma List<String>(ou qualquer coleção de Strings) através do addAllmétodo:

List<String> ls = ...
lo.addAll(ls)

No entanto, você deve perceber que a List<Object>e a List<String>não são equivalentes e nem a segunda é uma subclasse da primeira. O que é necessário é o conceito de um parâmetro do tipo covariante - ou seja, o <? extends T>bit.

Depois de ter isso, é simples pensar em cenários em que você também deseja contravariância (verifique a Comparableinterface).


4

Antes da resposta; Por favor, seja claro que

  1. Os genéricos apenas compilam o recurso de tempo para garantir TYPE_SAFETY, ele não estará disponível durante o RUNTIME.
  2. Somente uma referência com Generics forçará o tipo de segurança; se a referência não declarada com genéricos, ela funcionará sem o tipo safty.

Exemplo:

List stringList = new ArrayList<String>();
stringList.add(new Integer(10)); // will be successful.

Espero que isso ajude você a entender o curinga de maneira mais clara.

//NOTE CE - Compilation Error
//      4 - For

class A {}

class B extends A {}

public class Test {

    public static void main(String args[]) {

        A aObj = new A();
        B bObj = new B();

        //We can add object of same type (A) or its subType is legal
        List<A> list_A = new ArrayList<A>();
        list_A.add(aObj);
        list_A.add(bObj); // A aObj = new B(); //Valid
        //list_A.add(new String()); Compilation error (CE);
        //can't add other type   A aObj != new String();


        //We can add object of same type (B) or its subType is legal
        List<B> list_B = new ArrayList<B>();
        //list_B.add(aObj); CE; can't add super type obj to subclass reference
        //Above is wrong similar like B bObj = new A(); which is wrong
        list_B.add(bObj);



        //Wild card (?) must only come for the reference (left side)
        //Both the below are wrong;   
        //List<? super A> wildCard_Wrongly_Used = new ArrayList<? super A>();
        //List<? extends A> wildCard_Wrongly_Used = new ArrayList<? extends A>();


        //Both <? extends A>; and <? super A> reference will accept = new ArrayList<A>
        List<? super A> list_4__A_AND_SuperClass_A = new ArrayList<A>();
                        list_4__A_AND_SuperClass_A = new ArrayList<Object>();
                      //list_4_A_AND_SuperClass_A = new ArrayList<B>(); CE B is SubClass of A
                      //list_4_A_AND_SuperClass_A = new ArrayList<String>(); CE String is not super of A  
        List<? extends A> list_4__A_AND_SubClass_A = new ArrayList<A>();
                          list_4__A_AND_SubClass_A = new ArrayList<B>();
                        //list_4__A_AND_SubClass_A = new ArrayList<Object>(); CE Object is SuperClass of A


        //CE; super reference, only accepts list of A or its super classes.
        //List<? super A> list_4__A_AND_SuperClass_A = new ArrayList<String>(); 

        //CE; extends reference, only accepts list of A or its sub classes.
        //List<? extends A> list_4__A_AND_SubClass_A = new ArrayList<Object>();

        //With super keyword we can use the same reference to add objects
        //Any sub class object can be assigned to super class reference (A)                  
        list_4__A_AND_SuperClass_A.add(aObj);
        list_4__A_AND_SuperClass_A.add(bObj); // A aObj = new B();
        //list_4__A_AND_SuperClass_A.add(new Object()); // A aObj != new Object(); 
        //list_4__A_AND_SuperClass_A.add(new String()); CE can't add other type

        //We can't put anything into "? extends" structure. 
        //list_4__A_AND_SubClass_A.add(aObj); compilation error
        //list_4__A_AND_SubClass_A.add(bObj); compilation error
        //list_4__A_AND_SubClass_A.add("");   compilation error

        //The Reason is below        
        //List<Apple> apples = new ArrayList<Apple>();
        //List<? extends Fruit> fruits = apples;
        //fruits.add(new Strawberry()); THIS IS WORNG :)

        //Use the ? extends wildcard if you need to retrieve object from a data structure.
        //Use the ? super wildcard if you need to put objects in a data structure.
        //If you need to do both things, don't use any wildcard.


        //Another Solution
        //We need a strong reference(without wild card) to add objects 
        list_A = (ArrayList<A>) list_4__A_AND_SubClass_A;
        list_A.add(aObj);
        list_A.add(bObj);

        list_B = (List<B>) list_4__A_AND_SubClass_A;
        //list_B.add(aObj); compilation error
        list_B.add(bObj);

        private Map<Class<? extends Animal>, List<? extends Animal>> animalListMap;

        public void registerAnimal(Class<? extends Animal> animalClass, Animal animalObject) {

            if (animalListMap.containsKey(animalClass)) {
                //Append to the existing List
                 /*    The ? extends Animal is a wildcard bounded by the Animal class. So animalListMap.get(animalObject);
                 could return a List<Donkey>, List<Mouse>, List<Pikachu>, assuming Donkey, Mouse, and Pikachu were all sub classes of Animal. 
                 However, with the wildcard, you are telling the compiler that you don't care what the actual type is as long as it is a sub type of Animal.      
                 */   
                //List<? extends Animal> animalList = animalListMap.get(animalObject);
                //animalList.add(animalObject);  //Compilation Error because of List<? extends Animal>
                List<Animal> animalList = animalListMap.get(animalObject);
                animalList.add(animalObject);      


            } 
    }

    }
}



1
Boa explicação com código. Mas se você tivesse usado comentários no código fora do bloco de código, seria melhor para visualização e mais legível.
Prabhu

3

Um curinga com um limite superior se parece com "? Extends Type" e representa a família de todos os tipos que são subtipos de Type, sendo o tipo Type incluído. Tipo é chamado de limite superior.

Um curinga com um limite inferior se parece com "? Super Type" e representa a família de todos os tipos que são supertipos de Type, sendo o tipo Type incluído. Tipo é chamado limite inferior.


1

Você tem uma classe Parent e uma classe Child herdadas da classe Parent. A classe Parent é herdada de outra classe chamada GrandParent Class. Portanto, a ordem da herança é GrandParent> Parent> Child. Agora <? estende Parent> - Isso aceita a classe Parent ou a classe Child <? super Parent> - Isso aceita a classe Parent ou a classe GrandParent

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.