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
?
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
?
Antworten:
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 List
von HashMap
s sollte ein List
von Map
s 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 List
von HashMap
s kein List
von Map
s sein.
HashMap
liegt Map
es am Polymorphismus.
List
von HashMap
s ist kein List
von Map
s.
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)
HashMap
um eine Liste von Map
s, die beide Ihre Beispiele sind legal, wenn ich sie richtig gerade lese.
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 .)
List<String>
ist kein Subtyp von List<Object>
? - siehe zum Beispiel stackoverflow.com/questions/3246137/…
? extends
. Es erklärt auch nicht die Korrelation mit Super- / Subtypen oder Co- / Kontravarianz (falls vorhanden).
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:
Wenn Sie eine Klasse haben Automobile
, dann Car
und Truck
sind 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
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.
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? .
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 TreeMap
erbt 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
Dies ist wahrscheinlich offensichtlich, aber Sie haben möglicherweise bereits bemerkt, dass die Verwendung des extends
Schlü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 extend
fü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>());
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.
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 Set
von A
Objekten , dass ich ursprünglich mit dieser Unterschrift schrieb:
void myMethod(Set<A> set)
Aber es will es tatsächlich mit Set
s von Unterklassen von nennen A
. Das ist aber nicht erlaubt! (Der Grund dafür ist, myMethod
dass Objekte set
vom 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 set
der 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.
Wie Sie bereits erwähnt haben, gibt es zwei Möglichkeiten, eine Liste zu definieren:
List<? extends Map<String, String>>
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 List
Objekte eines bestimmten Typs gespeichert werden können, wurden Java-Generika eingeführt ? extends
. In # 1 List
kann 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.