Liste <Carte <Chaîne, Chaîne >> vs Liste <? étend la carte <Chaîne, Chaîne >>

129

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?

Ing. Fouad
la source
2
J'adore java mais c'est l'une des choses qui n'est pas si bonne ...
Mite Mitreski
4
J'ai l'impression que si on le lit comme "tout ce qui s'étend ...", cela devient clair.
Garbage
Incroyable, plus de 12K vues en 3 jours environ? !!
Eng.Fouad
5
Il a atteint la première page de Hacker News. Félicitations.
r3st0r3
1
@ Eng. Trouvé ici. Pas plus en première page, mais y était hier. news.ycombinator.net/item?id=3751901 (hier étant à la mi-dimanche ici en Inde.)
r3st0r3

Réponses:

180

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 Listof HashMaps devrait être a Listof Maps, 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 Listde HashMaps ne devrait pas être un Listde Maps.

vérité
la source
6
Pourtant, HashMapc'est un Mapdû au polymorphisme.
Eng.Fouad
46
Oui, mais un Listde HashMaps n'est pas un Listde Maps.
vérité du
1
C'est ce qu'on appelle la quantification bornée
Dan Burton
Bon exemple. Il convient également de noter que même si vous déclarez 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)
Raze
@Raze non, en fait, vous pouvez ajouter un HashMapà une liste de Maps, vos deux exemples sont légaux, si je les lis correctement.
vérité du
24

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

Tom Hawtin - ligne de pêche
la source
1
peut l'expliquer davantage? Je ne comprends pas ou un lien pour de bonnes pratiques?
Samir Mangroliya
3
@Samir Expliquez quoi? List<String>n'est pas un sous-type de List<Object>? - voir, par exemple, stackoverflow.com/questions/3246137/…
Tom Hawtin - tackline
2
Cela n'explique pas quelle est la différence entre avec ou sans ? extends. Il n'explique pas non plus la corrélation avec les super / sous-types ou la co / contravariance (s'il y en a).
Abel
16

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:

Covariance

Si vous avez une classe Automobile, alors Caret Trucksont 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

Contravariance

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.

Quand utiliser contra ou co-variance?

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

Alors qu'est-ce que c'est alors avec 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 TreeMaphé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

Quoi d'autre?

C'est probablement évident, mais vous avez peut-être déjà remarqué que l'utilisation du extendsmot - 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 extendsur 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>());
Abel
la source
Rien dans "Alors qu'est-ce que c'est alors avec ..." ne sera pas compilé.
NobleUplift
@NobleUplift: il est difficile de vous aider si vous ne fournissez pas le message d'erreur que vous obtenez. Pensez aussi, comme alternative, à poser une nouvelle question sur SO pour obtenir votre réponse, plus de chances de succès. Le code ci-dessus ne sont que des extraits, cela dépend de votre implémentation dans votre scénario.
Abel
Je n'ai pas de nouvelle question, j'ai des améliorations. 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.
NobleUplift
@NobleUplift: désolé, voyagez longtemps, ça va réparer quand je suis de retour, et merci d'avoir signalé l'erreur flagrante! :)
Abel
Pas de problème, je suis juste content que ça soit corrigé. J'ai effectué les modifications pour vous si vous souhaitez les approuver.
NobleUplift
4

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 Setdes Aobjets que j'ai initialement écrit avec cette signature:

void myMethod(Set<A> set)

Mais il veut réellement l'appeler avec Setdes sous-classes de A. Mais ce n'est pas autorisé! (La raison en est que cela myMethodpourrait ajouter des objets à ceux setqui sont de type A, mais pas du sous-type qui setest 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, setle 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.

Rörd
la source
0

Comme vous l'avez mentionné, il pourrait y avoir deux versions ci-dessous pour définir une liste:

  1. List<? extends Map<String, String>>
  2. 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 Listpeut contenir des objets d'un type donné, les génériques Java ont été introduits ? extends. Ainsi, dans le n ° 1, le Listpeut 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.

Funsuk Wangadu
la source