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 E
formulário para garantir que, quando buscar valores da coleção, todos eles serão E ou alguma subclasse (ou seja, é compatível). O drainTo
mé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 Child
um é 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
.
? extends InputStream
ou? super InputStream
então você pode usar umInputStream
como argumento.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)
Mas, o que aconteceria se você tentar fazer isso?
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,
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:
Agora você pode usá-lo assim:
Mas se você tentar implementar o mesmo código com coleções genéricas, não terá êxito:
Você obteria erros de compilador se tentar ...
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.
E você pode ler em
myNums
: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.
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.
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.
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.
Graças aos poderes de covariância e contravariância, isso funciona para um caso como este:
fonte
List<Object> myObjs = new List<Object();
(que está faltando o fechamento>
do segundoObject
).super.methodName
. Ao usar<? super E>
, significa "algo nasuper
direção" em oposição a algo naextends
direção. Exemplo:Object
está nasuper
direção deNumber
(uma vez que é uma super classe) eInteger
está naextends
direção (uma vez que se estendeNumber
).<? extends E>
defineE
como o limite superior: "Isso pode ser convertido emE
".<? super E>
defineE
como o limite inferior: "E
pode ser convertido para isso".fonte
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.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:
E
Mas realmente você deve conferir este livro: http://java.sun.com/docs/books/effective/
fonte
<? super E>
significaany object including E that is parent of E
<? extends E>
significaany object including E that is child of E .
fonte
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étodoCollection.addAll
:Assim como você gostaria de adicionar um
String
aList<Object>
:Você também deve poder adicionar uma
List<String>
(ou qualquer coleção deString
s) através doaddAll
método:No entanto, você deve perceber que a
List<Object>
e aList<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
Comparable
interface).fonte
Antes da resposta; Por favor, seja claro que
Exemplo:
Espero que isso ajude você a entender o curinga de maneira mais clara.
fonte
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.
fonte
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
fonte