Existe alguma diferença entre
List<Map<String, String>>
e
List<? extends Map<String, String>>
?
Se não houver diferença, qual é o benefício de usar ? extends
?
Existe alguma diferença entre
List<Map<String, String>>
e
List<? extends Map<String, String>>
?
Se não houver diferença, qual é o benefício de usar ? extends
?
Respostas:
A diferença é que, por exemplo, um
List<HashMap<String,String>>
é um
List<? extends Map<String,String>>
mas não um
List<Map<String,String>>
Assim:
void withWilds( List<? extends Map<String,String>> foo ){}
void noWilds( List<Map<String,String>> foo ){}
void main( String[] args ){
List<HashMap<String,String>> myMap;
withWilds( myMap ); // Works
noWilds( myMap ); // Compiler error
}
Você pensaria que um List
de HashMap
s deve ser um List
de Map
s, mas há uma boa razão para que não seja:
Suponha que você possa fazer:
List<HashMap<String,String>> hashMaps = new ArrayList<HashMap<String,String>>();
List<Map<String,String>> maps = hashMaps; // Won't compile,
// but imagine that it could
Map<String,String> aMap = Collections.singletonMap("foo","bar"); // Not a HashMap
maps.add( aMap ); // Perfectly legal (adding a Map to a List of Maps)
// But maps and hashMaps are the same object, so this should be the same as
hashMaps.add( aMap ); // Should be illegal (aMap is not a HashMap)
Portanto, é por isso que um List
de HashMap
s não deve ser um List
de Map
s.
HashMap
é Map
devido ao polimorfismo.
List
de HashMap
s não é um List
de Map
s.
List<Map<String,String>> maps = hashMaps;
e HashMap<String,String> aMap = new HashMap<String, String>();
, você ainda achará maps.add(aMap);
ilegal enquanto hashMaps.add(aMap);
legal. O objetivo é impedir a adição de tipos errados, mas não vai permitir a adição de direito tipos (compilador não pode determinar o tipo "certo" durante o tempo de compilação)
HashMap
a uma lista de Map
s, ambos os seus exemplos são legais, se eu os estiver lendo corretamente.
Você não pode atribuir expressões com tipos como List<NavigableMap<String,String>>
o primeiro.
(Se você quiser saber por que não pode atribuir List<String>
para List<Object>
ver um zilhão de outras perguntas no SO).
List<String>
não é um subtipo de List<Object>
? - veja, por exemplo, stackoverflow.com/questions/3246137/…
? extends
. Também não explica a correlação com super / subtipos ou co / contravariância (se houver).
O que estou perdendo nas outras respostas é uma referência a como isso se relaciona com co- e contravariância e subtipos e supertipos (isto é, polimorfismo) em geral e Java em particular. Isso pode ser bem entendido pelo OP, mas por precaução, aqui vai:
Se você tem uma aula Automobile
, então Car
eTruck
são seus subtipos. Qualquer carro pode ser atribuído a uma variável do tipo automóvel, isso é bem conhecido no OO e é chamado polimorfismo. A covariância refere-se ao uso desse mesmo princípio em cenários com genéricos ou delegados. Java ainda não possui delegados, portanto, o termo se aplica apenas aos genéricos.
Costumo pensar em covariância como polimorfismo padrão o que você esperaria que funcionasse sem pensar, porque:
List<Car> cars;
List<Automobile> automobiles = cars;
// You'd expect this to work because Car is-a Automobile, but
// throws inconvertible types compile error.
A razão do erro é, no entanto, correta: List<Car>
não herda List<Automobile>
e, portanto, não pode ser atribuída uma à outra. Somente os parâmetros de tipo genérico têm um relacionamento herdado. Pode-se pensar que o compilador Java simplesmente não é inteligente o suficiente para entender adequadamente o seu cenário. No entanto, você pode ajudar o compilador dando uma dica:
List<Car> cars;
List<? extends Automobile> automobiles = cars; // no error
O reverso da covariância é contravariância. Onde na covariância os tipos de parâmetros devem ter um relacionamento de subtipo, em contravariância, eles devem ter um relacionamento de supertipo. Isso pode ser considerado como um limite superior de herança: qualquer supertipo é permitido e incluindo o tipo especificado:
class AutoColorComparer implements Comparator<Automobile>
public int compare(Automobile a, Automobile b) {
// Return comparison of colors
}
Isso pode ser usado com Collections.sort :
public static <T> void sort(List<T> list, Comparator<? super T> c)
// Which you can call like this, without errors:
List<Car> cars = getListFromSomewhere();
Collections.sort(cars, new AutoColorComparer());
Você pode até chamá-lo com um comparador que compara objetos e o usa com qualquer tipo.
Um pouco AT, talvez, você não tenha perguntado, mas ajuda a entender a resposta à sua pergunta. Em geral, quando você obtém algo, use covariância e, quando coloca algo, use contravariância. Isso é melhor explicado em uma resposta à pergunta Stack Overflow Como a contravariância seria usada nos genéricos Java? .
List<? extends Map<String, String>>
Você usa extends
, então as regras de covariância se aplicam. Aqui você tem uma lista de mapas e cada item armazenado na lista deve ser Map<string, string>
ou derivar dele. A declaração List<Map<String, String>>
não pode derivar Map
, mas deve ser a Map
.
Portanto, o seguinte funcionará, porque TreeMap
herda de Map
:
List<Map<String, String>> mapList = new ArrayList<Map<String, String>>();
mapList.add(new TreeMap<String, String>());
mas isso não irá:
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new TreeMap<String, String>());
e isso também não funcionará, porque não satisfaz a restrição de covariância:
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new ArrayList<String>()); // This is NOT allowed, List does not implement Map
Isso provavelmente é óbvio, mas você já deve ter notado que o uso da extends
palavra - chave se aplica apenas a esse parâmetro e não ao restante. Ou seja, o seguinte não será compilado:
List<? extends Map<String, String>> mapList = new List<? extends Map<String, String>>();
mapList.add(new TreeMap<String, Element>()) // This is NOT allowed
Suponha que você queira permitir qualquer tipo no mapa, com uma chave como string, que possa ser usada extend
em cada parâmetro de tipo. Ou seja, suponha que você processe XML e deseje armazenar AttrNode, Element etc. em um mapa, você pode fazer algo como:
List<? extends Map<String, ? extends Node>> listOfMapsOfNodes = new...;
// Now you can do:
listOfMapsOfNodes.add(new TreeMap<Sting, Element>());
listOfMapsOfNodes.add(new TreeMap<Sting, CDATASection>());
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>(); mapList.add(new TreeMap<String, String>());
resulta em found: ? extends java.util.Map<java.lang.String,java.lang.String> required: class or interface without bounds
. List<Map<String, String>> mapList = new ArrayList<Map<String, String>>(); mapList.add(new TreeMap<String, String>());
funciona perfeitamente. O último exemplo está obviamente correto.
Hoje, eu usei esse recurso, então aqui está meu novo exemplo da vida real. (Alterei os nomes de classe e método para genéricos, para que eles não se distraiam do ponto real.)
Eu tenho um método que deve aceitar um Set
dos A
objetos que eu escrevi originalmente com esta assinatura:
void myMethod(Set<A> set)
Mas ele realmente quer chamá-lo com Set
s de subclasses de A
. Mas isso não é permitido! (A razão para isso é: myMethod
poderia adicionar objetos àqueles set
que são do tipo A
, mas não do subtipo queset
os objetos são declarados no site do chamador. Portanto, isso poderia interromper o sistema de tipos, se fosse possível.)
Agora, aqui estão os genéricos para o resgate, porque ele funciona conforme o esperado se eu usar essa assinatura de método:
<T extends A> void myMethod(Set<T> set)
ou mais curto, se você não precisar usar o tipo real no corpo do método:
void myMethod(Set<? extends A> set)
Dessa forma, set
o tipo de se torna uma coleção de objetos do subtipo real de A
, portanto, é possível usá-lo com subclasses sem pôr em risco o sistema de tipos.
Como você mencionou, pode haver duas versões abaixo da definição de uma lista:
List<? extends Map<String, String>>
List<?>
2 é muito aberto. Pode conter qualquer tipo de objeto. Isso pode não ser útil caso você queira ter um mapa de um determinado tipo. Caso alguém acidentalmente coloque um tipo diferente de mapa, por exemplo,Map<String, int>
,. Seu método de consumidor pode quebrar.
Para garantir que List
possam conter objetos de um determinado tipo, os genéricos Java foram introduzidos ? extends
. Assim, em # 1, o List
pode conter qualquer objeto derivado do Map<String, String>
tipo. Adicionar qualquer outro tipo de dados geraria uma exceção.