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
?
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
?
Réponses:
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
}
On pourrait penser que a List
of HashMap
s devrait être a List
of Map
s, mais il y a une bonne raison pour laquelle ce n'est pas le cas:
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 a List
de HashMap
s ne devrait pas être un List
de Map
s.
HashMap
c'est un Map
dû au polymorphisme.
List
de HashMap
s n'est pas un List
de Map
s.
List<Map<String,String>> maps = hashMaps;
et HashMap<String,String> aMap = new HashMap<String, String>();
, vous constaterez toujours que maps.add(aMap);
c'est illégal tant que hashMaps.add(aMap);
c'est légal. Le but est d'empêcher l'ajout de types incorrects, mais cela n'autorisera pas l'ajout de types corrects (le compilateur ne peut pas déterminer le type «bon» pendant la compilation)
HashMap
à une liste de Map
s, vos deux exemples sont légaux, si je les lis correctement.
Vous ne pouvez pas attribuer d'expressions avec des types tels que List<NavigableMap<String,String>>
le premier.
(Si vous voulez savoir pourquoi vous ne pouvez pas assigner List<String>
à List<Object>
voir un million d' autres questions sur SO.)
List<String>
n'est pas un sous-type de List<Object>
? - voir, par exemple, stackoverflow.com/questions/3246137/…
? extends
. Il n'explique pas non plus la corrélation avec les super / sous-types ou la co / contravariance (s'il y en a).
Ce qui me manque dans les autres réponses, c'est une référence à la façon dont cela se rapporte aux co- et contravariances et aux sous-et supertypes (c'est-à-dire au polymorphisme) en général et à Java en particulier. Cela peut être bien compris par l'OP, mais juste au cas où, le voici:
Si vous avez une classe Automobile
, alors Car
et Truck
sont leurs sous-types. N'importe quelle voiture peut être affectée à une variable de type Automobile, ceci est bien connu en OO et s'appelle polymorphisme. La 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és, donc le terme s'applique uniquement aux génériques.
J'ai tendance à considérer la covariance comme un polymorphisme standard, ce à quoi vous vous attendriez 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: n'hérite List<Car>
pas de List<Automobile>
et ne peut donc pas être attribuée les uns aux autres. Seuls les paramètres de type générique ont une relation d'héritage. 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, par contre, ils doivent avoir une relation de supertype. Cela peut être considéré comme une limite supérieure d'héritage: 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
}
Cela 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 des objets et l'utiliser avec n'importe quel type.
Un peu OT peut-être, vous ne l'avez pas demandé, mais cela aide à comprendre la réponse à votre question. En général, lorsque vous obtenez quelque chose, utilisez la covariance et lorsque vous mettez quelque chose, utilisez la contravariance. Ceci est mieux expliqué dans une réponse à la question de Stack Overflow Comment la contravariance serait-elle utilisée dans les génériques Java? .
List<? extends Map<String, String>>
Vous utilisez extends
, donc les règles de 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ériver. L'instruction List<Map<String, String>>
ne peut pas dériver de Map
, mais doit être un Map
.
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
C'est probablement évident, mais vous avez peut-être déjà remarqué que l'utilisation du extends
mot - clé ne s'applique qu'à ce paramètre et non au reste. Par exemple, 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 dans la carte, avec une clé comme chaîne, que 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 faire quelque chose comme:
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>());
résulte en 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>());
fonctionne parfaitement. Le dernier exemple est évidemment correct.
Aujourd'hui, j'ai utilisé cette fonctionnalité, voici donc mon exemple réel très récent. (J'ai changé les noms de classe et de méthode en noms génériques afin qu'ils ne détournent pas l'attention du point réel.)
J'ai une méthode qui est destinée à accepter un Set
des A
objets que j'ai initialement écrit avec cette signature:
void myMethod(Set<A> set)
Mais il veut réellement l'appeler avec Set
des sous-classes de A
. Mais ce n'est pas autorisé! (La raison en est que cela myMethod
pourrait ajouter des objets à ceux set
qui sont de type A
, mais pas du sous-type qui set
est déclaré que les objets sont sur le site de l'appelant. Cela pourrait donc casser le système de types si c'était possible.)
Maintenant, voici les génériques à la rescousse, car cela fonctionne comme prévu si j'utilise cette signature de méthode à la place:
<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, set
le type de s devient une collection d'objets du sous-type réel de A
, il devient donc 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 pour définir 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 au cas où vous voudriez avoir une carte d'un type donné. Au cas où quelqu'un mettrait accidentellement un autre type de carte, par exemple Map<String, int>
,. Votre méthode de consommation pourrait casser.
Afin de s'assurer que List
peut contenir des objets d'un type donné, les génériques Java ont été introduits ? extends
. Ainsi, dans le n ° 1, le List
peut contenir n'importe quel objet dérivé du Map<String, String>
type. L'ajout de tout autre type de données lèverait une exception.