Neste exemplo:
import java.util.*;
public class Example {
static void doesntCompile(Map<Integer, List<? extends Number>> map) {}
static <T extends Number> void compiles(Map<Integer, List<T>> map) {}
static void function(List<? extends Number> outer)
{
doesntCompile(new HashMap<Integer, List<Integer>>());
compiles(new HashMap<Integer, List<Integer>>());
}
}
doesntCompile()
falha ao compilar com:
Example.java:9: error: incompatible types: HashMap<Integer,List<Integer>> cannot be converted to Map<Integer,List<? extends Number>>
doesntCompile(new HashMap<Integer, List<Integer>>());
^
enquanto compiles()
é aceito pelo compilador.
Esta resposta explica que a única diferença é que <? ...>
, ao contrário ,<T ...>
permite fazer referência ao tipo mais tarde, o que não parece ser o caso.
Qual é a diferença entre <? extends Number>
e <T extends Number>
neste caso e por que o primeiro não é compilado?
Respostas:
Definindo o método com a seguinte assinatura:
e invocando-o como:
Nos jls §8.1.2 , encontramos (parte interessante em negrito por mim):
Em outras palavras, o tipo
T
é comparado com o tipo de entrada e atribuídoInteger
. A assinatura se tornará efetivamentestatic void compiles(Map<Integer, List<Integer>> map)
.Quando se trata de
doesntCompile
método, jls define regras de subtipagem ( §4.5.1 , negrito por mim):Isso significa que, de
? extends Number
fato, contémInteger
ou atéList<? extends Number>
contémList<Integer>
, mas não é o caso deMap<Integer, List<? extends Number>>
eMap<Integer, List<Integer>>
. Mais sobre esse tópico pode ser encontrado neste tópico do SO . Você ainda pode fazer a versão com?
curinga declarar que espera um subtipo deList<? extends Number>
:fonte
? extends Number
mais do que? extends Numeric
. [2] Sua afirmação de que "não é o caso da Lista <? Estende o Número> e da Lista <Integer>" está incorreta. Como o @VinceEmigh já apontou, você pode criar um métodostatic void demo(List<? extends Number> lst) { }
e chamá-lo assimdemo(new ArrayList<Integer>());
ou assimdemo(new ArrayList<Float>());
, e o código compila e executa OK. Ou talvez eu esteja interpretando mal ou entendendo mal o que você declarou?List<? extends Number>
como um parâmetro de tipo de todo o mapa, não ele próprio. Muito obrigado pelo comentário.List<Number>
não contémList<Integer>
. Suponha que você tenha uma funçãostatic void check(List<Number> numbers) {}
. Ao invocar comcheck(new ArrayList<Integer>());
ele não compila, você deve definir o método comostatic void check(List<? extends Number> numbers) {}
. Com o mapa é o mesmo, mas com mais aninhamento.Number
é um parâmetro de tipo da lista e você precisa adicionar? extends
para torná-lo covariante,List<? extends Number>
é um parâmetro de tipoMap
e também precisa? extends
de covariância.Na chamada:
T é correspondido a Inteiro, portanto, o tipo do argumento é a
Map<Integer,List<Integer>>
. Não é o caso do métododoesntCompile
: o tipo do argumento permaneceMap<Integer, List<? extends Number>>
independentemente do argumento real na chamada; e isso não é atribuível a partir deHashMap<Integer, List<Integer>>
.ATUALIZAR
No
doesntCompile
método, nada impede que você faça algo assim:Então, obviamente, ele não pode aceitar a
HashMap<Integer, List<Integer>>
como argumento.fonte
doesntCompile
? Apenas curioso sobre isso.doesntCompile(new HashMap<Integer, List<? extends Number>>());
funcionaria, como fariadoesntCompile(new HashMap<>());
.HashMap<Integer, List<Integer>>
" você poderia explicar por que não pode ser atribuído a partir dele?Exemplo simplificado de demonstração. O mesmo exemplo pode ser visualizado como abaixo.
List<Pair<? extends Number>>
é um tipo de curinga de vários níveis, enquantoList<? extends Number>
é um tipo de curinga padrão.Instanciações concretas válidas do tipo curinga
List<? extends Number>
incluemNumber
e quaisquer subtipos deNumber
considerando que, no caso deList<Pair<? extends Number>>
que seja um argumento de tipo de argumento de tipo e ele próprio tenha uma instanciação concreta do tipo genérico.Os genéricos são invariantes, portanto,
Pair<? extends Number>
o tipo de curinga só pode aceitarPair<? extends Number>>
. O tipo interno? extends Number
já é covariante. Você precisa tornar o tipo de anexo como covariável para permitir covariância.fonte
<Pair<Integer>>
isso não funciona,<Pair<? extends Number>>
mas funciona<T extends Number> <Pair<T>>
?T
vs?
. Parte do problema é que, quando Andronicus alcança o ponto vital de sua explicação, ele adia outro tópico que usa apenas exemplos triviais. Eu esperava obter uma resposta mais clara e completa aqui.Eu recomendo que você procure na documentação de curingas genéricos, especialmente diretrizes para o uso de curingas
Falando francamente do seu método #doesntCompile
e ligue como
É fundamentalmente incorreto
Vamos adicionar a implementação legal :
É muito bom, porque Double estende Number, então put também
List<Double>
é absolutamente bomList<Integer>
, certo?No entanto, você ainda acha que é legal passar aqui
new HashMap<Integer, List<Integer>>()
do seu exemplo?O compilador não pensa assim e está fazendo o possível para evitar tais situações.
Tente fazer a mesma implementação com o método #compile e o compilador obviamente não permitirá que você coloque uma lista de duplas no mapa.
Basicamente, você não pode colocar nada, mas
List<T>
é por isso que é seguro chamar esse método comnew HashMap<Integer, List<Integer>>()
ounew HashMap<Integer, List<Double>>()
ounew HashMap<Integer, List<Long>>()
ounew HashMap<Integer, List<Number>>()
.Então, em poucas palavras, você está tentando trapacear com o compilador e ele se defende bastante contra esse tipo de trapaça.
Nota: a resposta enviada por Maurice Perry está absolutamente correta. Só não tenho certeza se está claro o suficiente, então tentei (realmente espero que eu conseguisse) adicionar uma postagem mais extensa.
fonte