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
?
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
?
Risposte:
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 List
of HashMap
s debba essere a List
of Map
s, 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 List
of HashMap
s non dovrebbe essere a List
of Map
s.
HashMap
è Map
dovuto al polimorfismo.
List
di HashMap
s non è a List
di Map
s.
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)
HashMap
a un elenco di Map
s, entrambi i tuoi esempi sono legali, se li sto leggendo correttamente.
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.)
List<String>
non è un sottotipo di List<Object>
? - vedi, ad esempio, stackoverflow.com/questions/3246137/…
? extends
. Né spiega la correlazione con super / sottotipi o co / contravarianza (se presente).
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:
Se hai una classe Automobile
, allora Car
e Truck
sono 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
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.
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?.
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é TreeMap
eredita 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
Questo è probabilmente ovvio, ma potresti aver già notato che l'uso della extends
parola 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 extend
su 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>());
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.
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 Set
di A
oggetti che ho scritto originariamente con questa firma:
void myMethod(Set<A> set)
Ma vuole effettivamente chiamarlo con Set
s di sottoclassi di A
. Ma questo non è permesso! (La ragione di ciò è che myMethod
potrebbe aggiungere oggetti set
di 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, set
il 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.
Come hai detto, potrebbero esserci due versioni seguenti per definire un Elenco:
List<? extends Map<String, String>>
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 List
possano contenere oggetti di un determinato tipo, sono stati introdotti i generici Java ? extends
. Quindi in # 1, List
può contenere qualsiasi oggetto derivato dal Map<String, String>
tipo. L'aggiunta di qualsiasi altro tipo di dati genererebbe un'eccezione.