List <Map <String, String >> vs List <? erweitert Map <String, String >>

130

Gibt es einen Unterschied zwischen

List<Map<String, String>>

und

List<? extends Map<String, String>>

?

Wenn es keinen Unterschied gibt, welchen Nutzen hat die Verwendung ? extends?

Eng.Fouad
quelle
2
Ich liebe Java, aber das ist eines der Dinge, die nicht so gut sind ...
Mite Mitreski
4
Ich habe das Gefühl, wenn wir es als "alles, was sich ausdehnt ..." lesen, wird es klar.
Müll
Unglaublich, über 12K Aufrufe in ca. 3 Tagen? !!
Eng.Fouad
5
Es erreichte die Hacker News Titelseite. Glückwunsch.
r3st0r3
1
@ Eng.Found hier ist es. Nicht mehr auf der Titelseite, war aber gestern da. news.ycombinator.net/item?id=3751901 (gestern war Mitte Sonntag hier in Indien.)
r3st0r3

Antworten:

180

Der Unterschied besteht darin, dass zum Beispiel a

List<HashMap<String,String>>

ist ein

List<? extends Map<String,String>>

aber nicht a

List<Map<String,String>>

So:

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
}

Sie würden denken, ein Listvon HashMaps sollte ein Listvon Maps sein, aber es gibt einen guten Grund, warum es nicht so ist:

Angenommen, Sie könnten Folgendes tun:

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)

Deshalb sollte ein Listvon HashMaps kein Listvon Maps sein.

Wahrhaftigkeit
quelle
6
Trotzdem HashMapliegt Mapes am Polymorphismus.
Eng.Fouad
46
Richtig, aber ein Listvon HashMaps ist kein Listvon Maps.
Wahrhaftigkeit
1
Dies nennt man Bounded Quantification
Dan Burton
Gutes Beispiel. Erwähnenswert ist auch, dass Sie selbst dann, wenn Sie List<Map<String,String>> maps = hashMaps; und deklarieren HashMap<String,String> aMap = new HashMap<String, String>();, feststellen werden, dass dies maps.add(aMap);illegal ist, solange hashMaps.add(aMap);es legal ist. Der Zweck ist es, das Hinzufügen falscher Typen zu verhindern, aber das Hinzufügen von richtigen Typen wird nicht zugelassen (der Compiler kann den "richtigen" Typ während der Kompilierungszeit nicht bestimmen)
Raze
@Raze nein, eigentlich Sie können eine hinzufügen , HashMapum eine Liste von Maps, die beide Ihre Beispiele sind legal, wenn ich sie richtig gerade lese.
Wahrhaftigkeit
24

Sie können keine Ausdrücke mit Typen wie List<NavigableMap<String,String>>dem ersten zuweisen .

(Wenn Sie wissen möchten, warum Sie nicht zuweisen können List<String>, List<Object>dass zig andere Fragen zu SO angezeigt werden .)

Tom Hawtin - Tackline
quelle
1
kann es weiter erklären? Ich kann nicht verstehen oder einen Link für gute Praxis?
Samir Mangroliya
3
@ Samir Erklären Sie was? List<String>ist kein Subtyp von List<Object>? - siehe zum Beispiel stackoverflow.com/questions/3246137/…
Tom Hawtin - Tackline
2
Dies erklärt nicht, was der Unterschied zwischen mit oder ohne ist ? extends. Es erklärt auch nicht die Korrelation mit Super- / Subtypen oder Co- / Kontravarianz (falls vorhanden).
Abel
16

Was mir in den anderen Antworten fehlt, ist ein Hinweis darauf, wie sich dies auf Co- und Kontravarianz sowie Sub- und Supertypen (dh Polymorphismus) im Allgemeinen und auf Java im Besonderen bezieht. Dies mag vom OP gut verstanden werden, aber nur für den Fall, hier ist es:

Kovarianz

Wenn Sie eine Klasse haben Automobile, dann Carund Trucksind ihre Untertypen. Jedes Auto kann einer Variablen vom Typ Automobil zugeordnet werden. Dies ist in OO bekannt und wird als Polymorphismus bezeichnet. Kovarianz bezieht sich auf die Verwendung des gleichen Prinzips in Szenarien mit Generika oder Delegierten. Java hat (noch) keine Delegaten, daher gilt der Begriff nur für Generika.

Ich neige dazu, Kovarianz als Standardpolymorphismus zu betrachten, was Sie erwarten würden, ohne nachzudenken, weil:

List<Car> cars;
List<Automobile> automobiles = cars;
// You'd expect this to work because Car is-a Automobile, but
// throws inconvertible types compile error.

Der Grund des Fehlers ist jedoch richtig: erbt List<Car> nicht von List<Automobile>und kann daher nicht einander zugeordnet werden. Nur die generischen Typparameter haben eine Vererbungsbeziehung. Man könnte denken, dass der Java-Compiler einfach nicht klug genug ist, um Ihr Szenario dort richtig zu verstehen. Sie können dem Compiler jedoch helfen, indem Sie ihm einen Hinweis geben:

List<Car> cars;
List<? extends Automobile> automobiles = cars;   // no error

Kontravarianz

Die Umkehrung der Kovarianz ist die Kontravarianz. Während in der Kovarianz die Parametertypen eine Subtypbeziehung haben müssen, müssen sie in der Kontravarianz eine Supertypbeziehung haben. Dies kann als Vererbungsobergrenze betrachtet werden: Jeder Supertyp ist zulässig und enthält den angegebenen Typ:

class AutoColorComparer implements Comparator<Automobile>
    public int compare(Automobile a, Automobile b) {
        // Return comparison of colors
    }

Dies kann mit Collections.sort verwendet werden :

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

Sie können es sogar mit einem Vergleicher aufrufen, der Objekte vergleicht und mit einem beliebigen Typ verwendet.

Wann sollte Kontra oder Co-Varianz verwendet werden?

Ein bisschen OT vielleicht haben Sie nicht gefragt, aber es hilft zu verstehen, Ihre Frage zu beantworten. Wenn Sie etwas bekommen , verwenden Sie im Allgemeinen Kovarianz und wenn Sie etwas setzen , verwenden Sie Kontravarianz. Dies lässt sich am besten in einer Antwort auf die Frage zum Stapelüberlauf erklären. Wie würde Kontravarianz in Java-Generika verwendet? .

Also was ist es dann mit List<? extends Map<String, String>>

Sie verwenden extends, daher gelten die Regeln für die Kovarianz . Hier haben Sie eine Liste von Karten und jedes Element, das Sie in der Liste speichern, muss ein Map<string, string>oder davon abgeleitet sein. Die Aussage List<Map<String, String>>kann ableiten nicht aus Map, sondern müssen ein Map .

Daher funktioniert Folgendes, da TreeMaperbt von Map:

List<Map<String, String>> mapList = new ArrayList<Map<String, String>>();
mapList.add(new TreeMap<String, String>());

aber das wird nicht:

List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new TreeMap<String, String>());

und dies wird auch nicht funktionieren, da es die Kovarianzbedingung nicht erfüllt:

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

Was sonst?

Dies ist wahrscheinlich offensichtlich, aber Sie haben möglicherweise bereits bemerkt, dass die Verwendung des extendsSchlüsselworts nur für diesen Parameter und nicht für den Rest gilt. Dh folgendes wird nicht kompiliert:

List<? extends Map<String, String>> mapList = new List<? extends Map<String, String>>();
mapList.add(new TreeMap<String, Element>())  // This is NOT allowed

Angenommen, Sie möchten einen beliebigen Typ in der Map zulassen, mit einem Schlüssel als Zeichenfolge, den Sie extendfür jeden Typparameter verwenden können. Angenommen, Sie verarbeiten XML und möchten AttrNode, Element usw. in einer Map speichern. Sie können Folgendes tun:

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
quelle
Nichts in "Also, was ist es dann mit ..." wird kompiliert.
NobleUplift
@NobleUplift: Es ist schwierig, Ihnen zu helfen, wenn Sie die erhaltene Fehlermeldung nicht angeben. Erwägen Sie alternativ auch, eine neue Frage zu SO zu stellen, um Ihre Antwort zu erhalten, größere Erfolgschancen. Der obige Code ist nur ein Ausschnitt. Es hängt davon ab, ob Sie ihn in Ihrem Szenario implementiert haben.
Abel
Ich habe keine neue Frage, ich habe Verbesserungen. List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>(); mapList.add(new TreeMap<String, String>());führt zu 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>());funktioniert perfekt. Das letzte Beispiel ist offensichtlich richtig.
NobleUplift
@NobleUplift: Entschuldigung, lange Reisen, wird behoben, wenn ich zurück bin, und danke, dass Sie auf den grellen Fehler hingewiesen haben! :)
Abel
Kein Problem, ich bin nur froh, dass es behoben wird. Ich habe die Änderungen für Sie vorgenommen, wenn Sie sie genehmigen möchten.
NobleUplift
4

Heute habe ich diese Funktion verwendet. Hier ist mein sehr frisches Beispiel aus dem wirklichen Leben. (Ich habe Klassen- und Methodennamen in generische Namen geändert, damit sie nicht vom eigentlichen Punkt ablenken.)

Ich habe eine Methode , die eine akzeptieren ist gemeint Setvon AObjekten , dass ich ursprünglich mit dieser Unterschrift schrieb:

void myMethod(Set<A> set)

Aber es will es tatsächlich mit Sets von Unterklassen von nennen A. Das ist aber nicht erlaubt! (Der Grund dafür ist, myMethoddass Objekte setvom Typ hinzugefügt werden könnten A, jedoch nicht vom Subtypset Objekte als beim Standort des Anrufers deklariert sind. Dies könnte also das Typsystem beschädigen, wenn dies möglich wäre.)

Jetzt kommen hier Generika zur Rettung, denn es funktioniert wie beabsichtigt, wenn ich stattdessen diese Methodensignatur verwende:

<T extends A> void myMethod(Set<T> set)

oder kürzer, wenn Sie den tatsächlichen Typ im Methodenkörper nicht verwenden müssen:

void myMethod(Set<? extends A> set)

Auf diese Weise wird setder Typ zu einer Sammlung von Objekten des tatsächlichen Subtyps von A, sodass es möglich wird, dies mit Unterklassen zu verwenden, ohne das Typsystem zu gefährden.

Rörd
quelle
0

Wie Sie bereits erwähnt haben, gibt es zwei Möglichkeiten, eine Liste zu definieren:

  1. List<? extends Map<String, String>>
  2. List<?>

2 ist sehr offen. Es kann jeden Objekttyp enthalten. Dies ist möglicherweise nicht hilfreich, wenn Sie eine Karte eines bestimmten Typs haben möchten. Für den Fall, dass jemand versehentlich einen anderen Kartentyp einfügt, z Map<String, int>. Ihre Verbrauchermethode könnte brechen.

Um sicherzustellen, dass ListObjekte eines bestimmten Typs gespeichert werden können, wurden Java-Generika eingeführt ? extends. In # 1 Listkann das also jedes Objekt enthalten, das vom Map<String, String>Typ abgeleitet ist . Das Hinzufügen eines anderen Datentyps würde eine Ausnahme auslösen.

Funsuk Wangadu
quelle