web-dev-qa-db-fra.com

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

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?

125
Eng.Fouad

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 HashMaps devrait être un List de Maps, 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 HashMaps ne devrait pas être un List de Maps.

176
trutheality

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

25

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:

Covariance

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

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

Quand utiliser le contre ou la co-variance?

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

Alors qu'est-ce alors avec 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

Quoi d'autre?

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>());
16
Abel

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

4
Rörd

Comme vous l'avez mentionné, il pourrait y avoir deux versions ci-dessous de la définition d'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 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.

0
Java Spring Coder