Warum funktioniert die Verwendung von Collections.emptySet() mit Generika in der Zuweisung, aber nicht als Methodenparameter?
Lesezeit: 4 Minuten
Karl von L
Also, ich habe eine Klasse mit einem Konstruktor wie diesem:
public FilterList(Set<Integer> labels) {
...
}
und ich möchte einen neuen bauen FilterList Objekt mit einer leeren Menge. Dem Rat von Joshua Bloch in seinem Buch Effective Java folgend, möchte ich kein neues Objekt für die leere Menge erstellen; Ich werde nur verwenden Collections.emptySet() stattdessen:
FilterList emptyList = new FilterList(Collections.emptySet());
Dies gibt mir einen Fehler, der sich darüber beschwert java.util.Set<java.lang.Object> ist kein java.util.Set<java.lang.Integer>. Okay, wie wäre es damit:
FilterList emptyList = new FilterList((Set<Integer>)Collections.emptySet());
Dies gibt mir auch einen Fehler! Okay, wie wäre es damit:
Set<Integer> empty = Collections.emptySet();
FilterList emptyList = new FilterList(empty);
He, es funktioniert! Aber wieso? Schließlich hat Java keinen Typrückschluss, weshalb Sie in diesem Fall eine Warnung vor einer ungeprüften Konvertierung erhalten Set<Integer> foo = new TreeSet() Anstatt von Set<Integer> foo = new TreeSet<Integer>(). Aber Set<Integer> empty = Collections.emptySet(); funktioniert auch ohne Vorwarnung. Warum ist das so?
Alle folgenden Antworten sind richtig, aber was ich nicht verstehe, ist: Warum würden Sie Ihre Sammlung mit einer leeren Liste initialisieren, anstatt einen Standardkonstruktor ohne Parameter aufzurufen? Jede Sammlung, die ich kenne, hat einen Konstruktor mit einer vorhandenen Sammlung und einen leeren Konstruktor.
– Sean Patrick Floyd
17. Juni 2010 um 13:35 Uhr
Interessanterweise wird Folgendes kompiliert: FilterList emptyList = new FilterList((Set<Integer>)(Set<? extends Object>)Collections.emptySet());
– EricS
13. September 2012 um 18:43 Uhr
Noch ein ziemlich lustiges Beispiel: Set<Integer> emptySet = (Set<Integer>)Collections.emptySet(); kompiliert nicht.
– neo
27. November 2013 um 18:05 Uhr
Die kurze Antwort lautet: Das ist eine Einschränkung der Typinferenz im generischen System von Java. Es kann generische Typen von konkreten Variablen ableiten, aber nicht von Methodenparametern.
ich vermuten Dies liegt daran, dass Methoden abhängig von der Laufzeitklasse des besitzenden Objekts dynamisch abgesetzt werden, also zur Kompilierzeit (when alle allgemeine Informationen aufgelöst werden) können Sie nicht sicher wissen, welche Klasse der Methodenparameter sein wird, und können daher nicht ableiten. Variablendeklarationen sind nett und konstant, also können Sie.
Vielleicht kann jemand anderes mehr Details und / oder einen netten Link geben. 🙂
In jedem Fall können Sie die Typparameter immer explizit für generische Aufrufe wie folgt angeben:
Collections.<Integer>emptySet();
oder sogar mehrere Parameter auf einmal, zB
Collections.<String, Boolean>emptyMap(); // Returns a Map<String, Boolean>
Dies sieht oft ein wenig sauberer aus, als in Fällen, in denen die Inferenz nicht greift, zu casten.
+1 für eine gute Erklärung des Warum. Ich wünschte, man könnte in SO zwei Antworten richtig markieren 🙂
– jdmichal
17. Juni 2010 um 13:10 Uhr
Ich weiß, dass der Compiler beim Ableiten den Methodenaufruf nicht auf Kontext prüft, aber ich bin mir nicht sicher, warum. Zumindest für Privat bzw final Methoden sollte es möglich sein, Rückschlüsse zu ziehen.
– Hank Gay
17. Juni 2010 um 13:23 Uhr
@ Hank: oder static Methoden, die ebenfalls zur Kompilierzeit aufgelöst werden. Trotzdem – es nichtwas meiner Meinung nach der Hauptpunkt ist.
– Andrzej Doyle
17. Juni 2010 um 13:25 Uhr
„Ich vermute, das liegt daran, dass Methoden abhängig von der Laufzeitklasse des besitzenden Objekts dynamisch abgesetzt werden, sodass Sie zur Kompilierzeit nicht genau wissen können, welche Klasse der Methodenparameter sein wird, und daher nicht ableiten können .” Warum funktioniert dann die Version mit dem Typcast nicht?
– EricS
13. September 2012 um 18:39 Uhr
“Ich vermute, das liegt daran, dass Methoden abhängig von der Laufzeitklasse des besitzenden Objekts dynamisch versendet werden.” Ich bin mir nicht sicher, ob es dasselbe ist, aber welche Version einer überladenen Methode aufgerufen werden soll, wird zur Kompilierzeit bestimmt: kaufmännisches Und.space/blog/2007/1/12/java-overload
– Kipp
16. Mai 2016 um 14:57 Uhr
Andrej Fierbinteanu
Versuchen
FilterList emptyList = new FilterList(Collections.<Integer>emptySet());
Sie können den Typparameter für Methoden erzwingen, die ihn haben, in Fällen, in denen die Inferenz nicht gut genug ist, oder um Ihnen die Verwendung von Untertypen zu ermöglichen. zum Beispiel:
// forces use of ArrayList as parameter instead of the infered List
List<String> l = someObject.<ArrayList<String> methodThatTakesTypeParamForReturnType();
jdmichal
Sie möchten dies tun:
FilterList emptyList = new FilterList(java.util.Collections.<Integer>emptySet());
Das sagt die emptySet -Methode, die ihr generischer Parameter explizit verwenden sollte Integer statt der Vorgabe Object. Und ja, die Syntax ist dafür völlig irre und nicht intuitiv. 🙂
Diese Syntax war mir nicht bekannt, danke! Aber ich frage mich immer noch, warum diese Syntax in der Variablenzuweisung nicht notwendig ist.
– Karl von L
17. Juni 2010 um 13:10 Uhr
Siehe die Antwort von Andrzej Doyle. Ich glaube, es ist eine gute Erklärung.
– jdmichal
17. Juni 2010 um 13:12 Uhr
Du brauchst nicht new dort. Tatsächlich glaube ich nicht, dass es damit kompilieren wird.
– Andrej Fierbinteanu
17. Juni 2010 um 13:29 Uhr
Java hat einen Typrückschluss, es ist nur ziemlich begrenzt. Wenn Sie genau wissen möchten, wie es funktioniert und wo seine Grenzen liegen, ist dies eine wirklich gute Lektüre:
Alle folgenden Antworten sind richtig, aber was ich nicht verstehe, ist: Warum würden Sie Ihre Sammlung mit einer leeren Liste initialisieren, anstatt einen Standardkonstruktor ohne Parameter aufzurufen? Jede Sammlung, die ich kenne, hat einen Konstruktor mit einer vorhandenen Sammlung und einen leeren Konstruktor.
– Sean Patrick Floyd
17. Juni 2010 um 13:35 Uhr
Interessanterweise wird Folgendes kompiliert:
FilterList emptyList = new FilterList((Set<Integer>)(Set<? extends Object>)Collections.emptySet());
– EricS
13. September 2012 um 18:43 Uhr
Noch ein ziemlich lustiges Beispiel:
Set<Integer> emptySet = (Set<Integer>)Collections.emptySet();
kompiliert nicht.– neo
27. November 2013 um 18:05 Uhr