Eu estou usando genéricos há muito tempo, mas eu nunca usei construção como List
List
.
O que isso significa? Como usá-lo? Como se parece após o apagamento?
Eu também me pergunto: é algo padrão em programação genérica (programação de template?) Ou é apenas uma ‘invenção’ java? C #, por exemplo, permite construções semelhantes?
Essa construção é usada quando você deseja consumir itens de uma coleção em outra coleção. Por exemplo, você tem uma Stack
genérica e deseja adicionar um método popAll
que usa um parâmetro Collection como e insere todos os itens da pilha nela. Por bom senso, esse código deve ser legal:
Stack numberStack = new Stack (); Collection
mas ele compila apenas se você definir popAll
assim:
// Wildcard type for parameter that serves as an E consumer public void popAll(Collection super E> dst) { while (!isEmpty()) dst.add(pop()); }
O outro lado da moeda é que pushAll
deve ser definido assim:
// Wildcard type for parameter that serves as an E producer public void pushAll(Iterable extends E> src) { for (E e : src) push(e); }
Atualização: Josh Bloch propaga este mnemônico para ajudá-lo a lembrar qual tipo de caractere curinga usar:
PECS significa produtor-se estende, consumidor-super .
Para mais detalhes, veja Effective Java 2nd Ed., Item 28 .
Isso é chamado de “curinga delimitada”. Está muito bem explicado no tutorial oficial .
Como afirmado no tutorial, você sabe que a lista contém objects de exatamente um subtipo de T
Por exemplo, List extends Number>
List extends Number>
pode conter apenas Integer
s ou somente Long
s, mas não ambos.
Essas coisas são conhecidas, na teoria de tipos, como variância , com extends T>
extends T>
sendo uma notação co-variante, e super T>
super T>
sendo uma notação contra-variante. A explicação mais simples é essa ?
pode ser substituído por qualquer tipo estendendo T
na notação co-variante, e ?
pode ser substituído por qualquer tipo que T
se estenda na contra-variante.
Usar co e contra-variância é muito mais difícil do que parece inicialmente, particularmente porque a variância “alterna” dependendo da posição.
Um exemplo simples seria uma class de function. Digamos que você tenha uma function que receba um A
e retorne um B
A notação correta para isso seria dizer que A
é contra-variante e B
os co-variante. Para entender melhor como isso é o caso, vamos considerar um método – vamos chamá-lo de g
– que recebe essa class de function hipotética, onde f deve receber um Arc2D
e retornar uma Shape
.
Dentro de g
, esse f
é chamado de passar um Arc2D
e o valor de retorno é usado para inicializar uma Area
(que espera uma Shape
).
Agora, suponha que f
você passe receba qualquer Shape
e retorne um Rectangle2D
. Como um Arc2D
é também um Shape
, então g
não obterá um erro passando um Arc2D
para f
, e como um Rectangle2D
também é um Shape
, ele pode ser passado para o construtor de Area
.
Se você tentar inverter qualquer um dos desvios ou trocar os tipos esperado e real naquele exemplo, verá que ele falha. Eu não tenho tempo agora para escrever este código, e meu Java está bastante enferrujado, de qualquer forma, mas eu vou ver o que posso fazer depois – se ninguém tiver a gentileza de fazer isso primeiro.
O Java Generics FAQ tem uma boa explicação sobre os genéricos Java. Verifique a pergunta O que é um curinga limitado? o que explica o uso do constructo “? super T” com bons detalhes.