Lista <Mapa <String, String >> vs List <? estende Map <String, String >>

129

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?

Eng.Fouad
fonte
2
Eu amo java, mas esta é uma das coisas que não é bom ...
Mite Mitreski
4
Sinto que, se o lermos como "qualquer coisa que se estenda ...", fica claro.
Garbage
Incrível, mais de 12 mil visualizações em cerca de 3 dias ?!
Eng.Fouad
5
Chegou à primeira página do Hacker News. Parabéns.
R3st0r3
1
@ Eng.Found aqui está. Não estava mais na primeira página, mas estava lá ontem. news.ycombinator.net/item?id=3751901 (ontem sendo meados de domingo aqui na Índia.)
r3st0r3

Respostas:

180

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 Listde HashMaps deve ser um Listde Maps, 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 Listde HashMaps não deve ser um Listde Maps.

verdade
fonte
6
Ainda assim, HashMapé Mapdevido ao polimorfismo.
Eng.Fouad 21/03/12
46
Certo, mas um Listde HashMaps não é um Listde Maps.
trutheality
1
Isso é chamado quantificação limitada
Dan Burton
Bom exemplo. Também digno de nota é que, mesmo se você declarar 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)
Raze
@ Raze não, na verdade você pode adicionar um HashMapa uma lista de Maps, ambos os seus exemplos são legais, se eu os estiver lendo corretamente.
trutheality
24

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).

Tom Hawtin - linha de orientação
fonte
1
pode explicar mais isso? não consigo entender ou qualquer link para boas práticas?
Samir Mangroliya 21/03
3
@ Samir Explain what? List<String>não é um subtipo de List<Object>? - veja, por exemplo, stackoverflow.com/questions/3246137/…
Tom Hawtin - tackline
2
Isso não explica qual é a diferença entre com ou sem ? extends. Também não explica a correlação com super / subtipos ou co / contravariância (se houver).
Abel
16

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:

Covariância

Se você tem uma aula Automobile, então CareTruck 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

Contravariância

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.

Quando usar contra ou co-variação?

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? .

Então, o que é então com 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 TreeMapherda 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

O quê mais?

Isso provavelmente é óbvio, mas você já deve ter notado que o uso da extendspalavra - 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 extendem 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>());
Abel
fonte
Nada em "Então, o que acontece então com ..." será compilado.
NobleUplift
@ NobleUplift: é difícil ajudá-lo se você não fornecer a mensagem de erro recebida. Considere também, como alternativa, fazer uma nova pergunta no SO para obter sua resposta, maior chance de sucesso. O código acima é apenas trechos, depende de você ter implementado em seu cenário.
Abel
Não tenho uma nova pergunta, tenho melhorias. 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.
NobleUplift 13/08
@NobleUplift: desculpe, viajar muito tempo, será corrigido quando eu voltar, e obrigado por apontar o erro gritante! :)
Abel
Não tem problema, estou feliz que será corrigido. Fiz as edições para você, se você quiser aprová-las.
NobleUplift 17/09/13
4

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 Setdos Aobjetos que eu escrevi originalmente com esta assinatura:

void myMethod(Set<A> set)

Mas ele realmente quer chamá-lo com Sets de subclasses de A. Mas isso não é permitido! (A razão para isso é: myMethodpoderia adicionar objetos àqueles setque 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, seto 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.

Rörd
fonte
0

Como você mencionou, pode haver duas versões abaixo da definição de uma lista:

  1. List<? extends Map<String, String>>
  2. 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 Listpossam conter objetos de um determinado tipo, os genéricos Java foram introduzidos ? extends. Assim, em # 1, o Listpode conter qualquer objeto derivado do Map<String, String>tipo. Adicionar qualquer outro tipo de dados geraria uma exceção.

Funsuk Wangadu
fonte