Y a-t-il une différence entre
List<Map<String, String>>
et
List<? extends Map<String, String>>
?
S'il n'y a pas de différence, quel est l'avantage d'utiliser ? extends
?
La différence est que, par exemple, un
List<HashMap<String,String>>
est un
List<? extends Map<String,String>>
mais pas un
List<Map<String,String>>
Alors:
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
}
Vous penseriez qu'un List
de HashMap
s devrait être un List
de Map
s, mais il y a une bonne raison pour que ce ne soit pas:
Supposons que vous puissiez faire:
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)
C'est pourquoi un List
de HashMap
s ne devrait pas être un List
de Map
s.
Vous ne pouvez pas affecter d'expressions avec des types tels que List<NavigableMap<String,String>>
au premier.
(Si vous voulez savoir pourquoi vous ne pouvez pas attribuer List<String>
à List<Object>
voir a zillion autres questions sur SO.)
Ce qui me manque dans les autres réponses, c’est une référence à la relation entre cela et les co-et contravariances et les sous-et supertypes (c’est-à-dire le polymorphisme) en général et à Java en particulier. Ceci peut être bien compris par le PO, mais juste au cas où, il va ici:
Si vous avez une classe Automobile
, alors Car
et Truck
sont leurs sous-types. Toute voiture peut être affectée à une variable de type Automobile, elle est bien connue dans OO et s'appelle polymorphisme. Covariance fait référence à l'utilisation de ce même principe dans des scénarios avec des génériques ou des délégués. Java n'a pas (encore) de délégué), le terme ne s'applique donc qu'aux génériques.
J'ai tendance à penser à la covariance en tant que polymorphisme standard ce que vous vous attendriez à travailler sans réfléchir, car:
List<Car> cars;
List<Automobile> automobiles = cars;
// You'd expect this to work because Car is-a Automobile, but
// throws inconvertible types compile error.
La raison de l'erreur est cependant correcte: List<Car>
pas hériter de List<Automobile>
et ne peuvent donc pas être affectés l'un à l'autre. Seuls les paramètres de type générique ont une relation héritée. On pourrait penser que le compilateur Java n’est tout simplement pas assez intelligent pour bien comprendre votre scénario. Cependant, vous pouvez aider le compilateur en lui donnant un indice:
List<Car> cars;
List<? extends Automobile> automobiles = cars; // no error
L'inverse de la co-variance est la contravariance. Là où dans la covariance les types de paramètres doivent avoir une relation de sous-type, dans la contradiction, ils doivent avoir une relation de supertype. Cela peut être considéré comme un héritage supérieur-lié: tout supertype est autorisé et inclut le type spécifié:
class AutoColorComparer implements Comparator<Automobile>
public int compare(Automobile a, Automobile b) {
// Return comparison of colors
}
Ceci peut être utilisé avec 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());
Vous pouvez même l'appeler avec un comparateur qui compare les objets et l'utiliser avec n'importe quel type.
Un peu OT peut-être que vous n'avez pas posé la question, mais cela aide de comprendre votre réponse. En général, lorsque vous obtenez quelque chose, utilisez la covariance et lorsque vous put quelque chose, utilisez la contravariance. Ceci est mieux expliqué dans une réponse à la question de débordement de pile Comment la contravariance serait-elle utilisée dans Java génériques? .
List<? extends Map<String, String>>
Vous utilisez extends
, donc les règles pour covariance s'appliquent. Ici vous avez une liste de cartes et chaque élément que vous stockez dans la liste doit être un Map<string, string>
ou en dérive. La déclaration List<Map<String, String>>
ne peut pas dériver de Map
, mais doit être aMap
.
Par conséquent, ce qui suit fonctionnera, car TreeMap
hérite de Map
:
List<Map<String, String>> mapList = new ArrayList<Map<String, String>>();
mapList.add(new TreeMap<String, String>());
mais ce ne sera pas:
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new TreeMap<String, String>());
et cela ne fonctionnera pas non plus, car cela ne satisfait pas la contrainte de covariance:
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
Cela est probablement évident, mais vous avez peut-être déjà remarqué que l’utilisation du mot-clé extends
ne s’applique qu’à ce paramètre et pas au reste. C'est-à-dire que les éléments suivants ne seront pas compilés:
List<? extends Map<String, String>> mapList = new List<? extends Map<String, String>>();
mapList.add(new TreeMap<String, Element>()) // This is NOT allowed
Supposons que vous souhaitiez autoriser n'importe quel type de la carte, avec une clé sous forme de chaîne, vous pouvez utiliser extend
sur chaque paramètre de type. Par exemple, supposons que vous traitez du XML et que vous souhaitiez stocker AttrNode, Element, etc. dans une carte, vous pouvez effectuer les opérations suivantes:
List<? extends Map<String, ? extends Node>> listOfMapsOfNodes = new...;
// Now you can do:
listOfMapsOfNodes.add(new TreeMap<Sting, Element>());
listOfMapsOfNodes.add(new TreeMap<Sting, CDATASection>());
Aujourd'hui, j'ai utilisé cette fonctionnalité, voici donc mon exemple très récent de la vie réelle. (J'ai remplacé les noms de classe et de méthode par des noms génériques afin qu'ils ne gênent pas le propos.)
J'ai une méthode censée accepter un Set
de A
objets que j'ai écrit à l'origine avec cette signature:
void myMethod(Set<A> set)
Mais il veut vraiment l'appeler avec Set
s des sous-classes de A
. Mais ce n'est pas permis! (La raison en est que myMethod
pourrait ajouter des objets à set
de type A
, mais pas du sous-type auquel les objets de set
sont déclarés. être sur le site de l'appelant, donc cela pourrait casser le système de typage si cela était possible.)
Maintenant, voici les génériques à la rescousse, car cela fonctionne comme prévu si j'utilise plutôt cette signature de méthode:
<T extends A> void myMethod(Set<T> set)
ou plus court, si vous n'avez pas besoin d'utiliser le type réel dans le corps de la méthode:
void myMethod(Set<? extends A> set)
De cette façon, le type de set
devient une collection d'objets du sous-type actuel de A
, de sorte qu'il devient possible de l'utiliser avec des sous-classes sans mettre en danger le système de types.
Comme vous l'avez mentionné, il pourrait y avoir deux versions ci-dessous de la définition d'une liste:
List<? extends Map<String, String>>
List<?>
2 est très ouvert. Il peut contenir n'importe quel type d'objet. Cela peut ne pas être utile si vous souhaitez avoir une carte d'un type donné. Si quelqu'un met accidentellement un type de carte différent, par exemple, Map<String, int>
. Votre méthode de consommation pourrait casser.
Afin de garantir que List
puisse contenir des objets d'un type donné, Java génériques introduits ? extends
. Donc, dans # 1, le List
peut contenir n'importe quel objet dérivé de Map<String, String>
type. L'ajout de tout autre type de données lève une exception.