Elenco <Mappa <Stringa, Stringa >> vs Elenco <? estende la mappa <String, String >>


129

C'è qualche differenza tra

List<Map<String, String>>

e

List<? extends Map<String, String>>

?

Se non ci sono differenze, quali sono i vantaggi dell'utilizzo ? extends?


2
Adoro java ma questa è una delle cose che non è così buona ...
Mite Mitreski

4
Sento che se lo leggiamo come "tutto ciò che si estende ...", diventa chiaro.
Garbage

Incredibile, oltre 12.000 visualizzazioni in circa 3 giorni? !!
Ing

5
Raggiunse la prima pagina di Hacker News. Congratulazioni.
r3st0r3,

1
@ Eng.Found eccolo qui. Non più in prima pagina, ma era lì ieri. news.ycombinator.net/item?id=3751901 (ieri essendo metà domenica qui in India.)
r3st0r3

Risposte:


180

La differenza è che, ad esempio, a

List<HashMap<String,String>>

è un

List<? extends Map<String,String>>

ma non a

List<Map<String,String>>

Così:

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
}

Penseresti che a Listof HashMaps debba essere a Listof Maps, ma c'è una buona ragione per cui non lo è:

Supponiamo che tu possa fare:

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)

Quindi questo è il motivo per cui a Listof HashMaps non dovrebbe essere a Listof Maps.


6
Tuttavia, HashMapè Mapdovuto al polimorfismo.
Ing

46
Giusto, ma a Listdi HashMaps non è a Listdi Maps.
verità

1

Buon esempio. Vale anche la pena notare che anche se dichiari List<Map<String,String>> maps = hashMaps; e HashMap<String,String> aMap = new HashMap<String, String>();, troverai comunque maps.add(aMap);illegale mentre hashMaps.add(aMap);è legale. Lo scopo è impedire l'aggiunta di tipi errati, ma non consentirà l'aggiunta di tipi giusti (il compilatore non può determinare il tipo "giusto" durante il tempo di compilazione)
Raze

@Raze no, in realtà puoi aggiungere un HashMapa un elenco di Maps, entrambi i tuoi esempi sono legali, se li sto leggendo correttamente.
verità

24

Non è possibile assegnare espressioni con tipi come List<NavigableMap<String,String>>il primo.

(Se vuoi sapere perché non puoi assegnare List<String>per List<Object>vedere un milione di altre domande su SO.)


1
puoi spiegarlo ulteriormente? non riesco a capire o qualsiasi link per le buone pratiche?
Samir Mangroliya,

3
@Samir Spiegare cosa? List<String>non è un sottotipo di List<Object>? - vedi, ad esempio, stackoverflow.com/questions/3246137/…
Tom Hawtin - tackline

2
Questo non spiega quale sia la differenza tra con o senza ? extends. Né spiega la correlazione con super / sottotipi o co / contravarianza (se presente).
Abel,

16

Quello che mi manca nelle altre risposte è un riferimento a come ciò si collega alla co-e contraddizione e ai sottotipi e supertipi (cioè polimorfismo) in generale e Java in particolare. Questo può essere ben compreso dall'OP, ma per ogni evenienza, ecco qui:

covarianza

Se hai una classe Automobile, allora Care Trucksono i loro sottotipi. Qualsiasi automobile può essere assegnata a una variabile di tipo Automobile, questo è ben noto in OO e si chiama polimorfismo. Covarianza si riferisce all'utilizzo di questo stesso principio in scenari con generici o delegati. Java non ha delegati (ancora), quindi il termine si applica solo ai generici.

Tendo a considerare la covarianza come un polimorfismo standard ciò che ti aspetteresti di lavorare senza pensare, perché:

List<Car> cars;
List<Automobile> automobiles = cars;
// You'd expect this to work because Car is-a Automobile, but
// throws inconvertible types compile error.

Il motivo dell'errore è, tuttavia, corretto: List<Car> non eredita da List<Automobile>e quindi non può essere assegnato l'uno all'altro. Solo i parametri di tipo generico hanno una relazione ereditaria. Si potrebbe pensare che il compilatore Java semplicemente non sia abbastanza intelligente da capire correttamente il tuo scenario lì. Tuttavia, puoi aiutare il compilatore dandogli un suggerimento:

List<Car> cars;
List<? extends Automobile> automobiles = cars;   // no error

controvarianza

Il contrario della co-varianza è la contraddizione. Laddove nella covarianza i tipi di parametro devono avere una relazione di sottotipo, nella contraddizione devono avere una relazione di sottotipo. Questo può essere considerato come un'eredità limite superiore: qualsiasi supertipo è consentito e include il tipo specificato:

class AutoColorComparer implements Comparator<Automobile>
    public int compare(Automobile a, Automobile b) {
        // Return comparison of colors
    }

Questo può essere usato con 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());

Potresti anche chiamarlo con un comparatore che confronta gli oggetti e usarlo con qualsiasi tipo.

Quando usare il contra o la varianza?

Forse un po 'OT, non l'hai chiesto, ma aiuta a capire come rispondere alla tua domanda. In generale, quando ottieni qualcosa, usa la covarianza e quando metti qualcosa, usa la contraddizione. Questo è meglio spiegato in una risposta alla domanda Stack Overflow Come si usa la contravarianza nei generici Java?.

Allora di cosa si tratta List<? extends Map<String, String>>

Si utilizza extends, quindi si applicano le regole per la covarianza . Qui hai un elenco di mappe e ogni elemento che memorizzi nell'elenco deve essere Map<string, string>o derivare da esso. L'affermazione List<Map<String, String>>non può derivare Map, ma deve essere a Map .

Quindi, funzionerà quanto segue, poiché TreeMaperedita da Map:

List<Map<String, String>> mapList = new ArrayList<Map<String, String>>();
mapList.add(new TreeMap<String, String>());

ma questo non:

List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new TreeMap<String, String>());

e neanche questo funzionerà perché non soddisfa il vincolo di covarianza:

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

Cos'altro?

Questo è probabilmente ovvio, ma potresti aver già notato che l'uso della extendsparola chiave si applica solo a quel parametro e non al resto. Vale a dire, non verrà compilato quanto segue:

List<? extends Map<String, String>> mapList = new List<? extends Map<String, String>>();
mapList.add(new TreeMap<String, Element>())  // This is NOT allowed

Supponiamo che tu voglia consentire qualsiasi tipo nella mappa, con una chiave come stringa, che puoi usare extendsu ogni parametro di tipo. Cioè, supponiamo che tu elabori XML e desideri memorizzare AttrNode, Element ecc in una mappa, puoi fare qualcosa del tipo:

List<? extends Map<String, ? extends Node>> listOfMapsOfNodes = new...;

// Now you can do:
listOfMapsOfNodes.add(new TreeMap<Sting, Element>());
listOfMapsOfNodes.add(new TreeMap<Sting, CDATASection>());

Nulla in "Quindi cosa sarà allora con ..." compilerà.
NobleUplift,

@NobleUplift: è difficile aiutarti se non fornisci il messaggio di errore che ricevi. Considera anche, in alternativa, di porre una nuova domanda su SO per ottenere la tua risposta, maggiori possibilità di successo. Il codice sopra è solo frammenti, dipende che tu l'abbia implementato nel tuo scenario.
Abele,

Non ho una nuova domanda, ho dei miglioramenti. List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>(); mapList.add(new TreeMap<String, String>());risultati in 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>());funziona perfettamente. L'ultimo esempio è ovviamente corretto.
NobleUplift

@NobleUplift: scusa, viaggiando a lungo, risolverò quando torno e grazie per aver segnalato l'errore eclatante! :)
Abele,

Nessun problema, sono solo contento che sarà risolto. Ti ho fatto le modifiche se vuoi approvarle.
NobleUplift,

4

Oggi ho usato questa funzione, quindi ecco il mio nuovissimo esempio di vita reale. (Ho cambiato i nomi delle classi e dei metodi con quelli generici in modo che non distraggano dal punto reale.)

Ho un metodo che ha lo scopo di accettare una Setdi Aoggetti che ho scritto originariamente con questa firma:

void myMethod(Set<A> set)

Ma vuole effettivamente chiamarlo con Sets di sottoclassi di A. Ma questo non è permesso! (La ragione di ciò è che myMethodpotrebbe aggiungere oggetti setdi tipo A, ma non del sottotiposet gli oggetti vengono dichiarati sul sito del chiamante. Quindi, se possibile, ciò potrebbe interrompere il sistema di tipi.)

Ora ecco i generici in soccorso, perché funziona come previsto se uso invece questa firma del metodo:

<T extends A> void myMethod(Set<T> set)

o più breve, se non è necessario utilizzare il tipo effettivo nel corpo del metodo:

void myMethod(Set<? extends A> set)

In questo modo, setil tipo diventa una raccolta di oggetti del sottotipo reale di A, quindi diventa possibile usarlo con le sottoclassi senza mettere in pericolo il sistema dei tipi.


0

Come hai detto, potrebbero esserci due versioni seguenti per definire un Elenco:

  1. List<? extends Map<String, String>>
  2. List<?>

2 è molto aperto. Può contenere qualsiasi tipo di oggetto. Questo potrebbe non essere utile nel caso in cui desideri avere una mappa di un determinato tipo. Nel caso in cui qualcuno inserisca accidentalmente un diverso tipo di mappa, ad esempio Map<String, int>. Il tuo metodo di consumo potrebbe non funzionare.

Al fine di garantire che Listpossano contenere oggetti di un determinato tipo, sono stati introdotti i generici Java ? extends. Quindi in # 1, Listpuò contenere qualsiasi oggetto derivato dal Map<String, String>tipo. L'aggiunta di qualsiasi altro tipo di dati genererebbe un'eccezione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.