web-dev-qa-db-de.com

Warum löst Set.of () eine IllegalArgumentException aus, wenn die Elemente Duplikate sind?

In Java 9 wurden neue statische Factory-Methoden für die Set-Schnittstelle namens of () eingeführt, die mehrere Elemente oder sogar ein Array von Elementen akzeptieren.

Ich wollte eine Liste in eine Menge verwandeln, um doppelte Einträge in der Menge zu entfernen. Dies kann (vor Java 9) unter Verwendung von:

Set<String> set = new HashSet<>();
set.addAll(list);

Aber ich dachte, es wäre cool, diese neue statische Java 9-Factory-Methode zu verwenden:

Set.of(list.toArray())

dabei ist die list eine zuvor definierte Liste von Zeichenfolgen.

Aber leider hat Java eine IllegalArgumentException geworfen, als die Elemente Duplikate waren, wie auch im Javadoc der Methode angegeben. Warum ist das?

Edit : Diese Frage ist kein Duplikat einer anderen Frage zu einem konzeptionell äquivalenten Thema, der Map.of () -Methode, sondern deutlich anders. Nicht alle statischen factory of () -Methoden verhalten sich gleich. Mit anderen Worten, wenn ich etwas über die Set.of () -Methode frage, würde ich nicht auf eine Frage klicken, die sich mit der Map.of () -Methode befasst.

17
Benjamin

Die Factory-Methoden Set.of() erzeugen unveränderliche Sets für eine angegebene Anzahl von Elementen. 

In den Varianten, die eine feste Anzahl von Argumenten unterstützen (static <E> Set<E> of​(), static <E> Set<E> of​(E e1), static <E> Set<E> of​(E e1,E e2) usw.), ist die Anforderung, dass keine Duplikate vorhanden sind, leichter zu verstehen, wenn Sie die Methode aufrufen Set.of(a,b,c), Sie geben an, dass Sie ein unveränderliches Set von genau 3 Elementen erstellen möchten. Wenn also die Argumente Duplikate enthalten, ist es sinnvoll, Ihre Eingabe abzulehnen, anstatt ein kleineres Set.

Während die Set<E> of​(E... elements)-Variante unterschiedlich ist (wenn ein Set aus einer beliebigen Anzahl von Elementen erstellt werden kann), folgt sie der gleichen Logik der anderen Varianten. Wenn Sie n -Elemente an diese Methode übergeben, geben Sie an, dass Sie ein unveränderliches Set aus genau n -Elementen erstellen möchten, sodass Duplikate nicht zulässig sind.

Sie können immer noch ein Set aus einem List (mit potenziellen Duplikaten) in einem Einzeiler erstellen, indem Sie:

Set<String> set = new HashSet<>(list);

das war schon vor Java 9 verfügbar.

17
Eran

Set.of() ist eine kurze Möglichkeit, manuell eine kleine Set zu erstellen. In diesem Fall wäre es ein eklatanter Programmierfehler, wenn Sie ihm doppelte Werte geben, da Sie die Elemente selbst ausschreiben sollen. Das heißt Set.of("foo", "bar", "baz", "foo"); ist eindeutig ein Fehler des Programmierers.

Ihr cool Weg ist eigentlich ein wirklich schlechter Weg. Wenn Sie eine List in eine Set konvertieren möchten, können Sie dies mit Set<Foo> foo = new HashSet<>(myList); oder auf andere Weise tun (z. B. mit Streams und Sammeln von toSet()). Zu den Vorteilen gehört, dass Sie keine nutzlose toArray() ausführen, die Wahl Ihrer eigenen Set (Sie möchten möglicherweise, dass eine LinkedHashSet die Reihenfolge beibehält) usw. Zu den Nachteilen gehört, dass Sie einige weitere Zeichen des Codes eingeben müssen.

Die ursprüngliche Gestaltungsidee hinter den Methoden Set.of(), List.of() und Map.of() (und deren zahlreichen Überladungen) wird hier erläutert Was ist der Punkt überlasteter Convenience Factory-Methoden für Sammlungen in Java 9 und hier , wo dies erwähnt wird Der Fokus liegt auf kleinen Sammlungen , was in der gesamten internen API sehr verbreitet ist, sodass Leistungsvorteile erzielt werden können. Obwohl die Methoden derzeit an die varargs-Methode delegiert werden, die keinen Leistungsvorteil bietet, kann dieser leicht geändert werden (wobei nicht genau bekannt ist, was sich dort aufhält).

25
Kayaman

Sie erwarten, dass dies ein "Last-Wins" sein wird, genau wie HashSet, denke ich, aber dies war eine vorsätzliche Entscheidung (wie Stuart Marks - der Schöpfer dieser - erklärt). Er hat sogar ein Beispiel wie dieses:

Map.ofEntries(
   "!", "Eclamation"
   .... // lots of other entries
   ""
   "|", "VERTICAL_BAR"
);

Die Wahl ist, da dies fehleranfällig sein kann, sollten sie es verbieten.

Beachten Sie auch, dass Set.of() eine unveränderliche Set zurückgibt, sodass Sie Ihre Set in Folgendes einschließen können:

Collections.unmodifiableCollection(new HashSet<>(list))
6
Eugene

Das primäre Entwurfsziel der statischen Factory-Methoden List.of, Set.of, Map.of und Map.ofEntries besteht darin, Programmierern die Möglichkeit zu geben, diese Sammlungen zu erstellen, indem die Elemente explizit im Quellcode aufgeführt werden. Natürlich gibt es eine Tendenz zu einer kleinen Anzahl von Elementen oder Einträgen, da diese häufiger sind, aber das relevante Merkmal hier ist, dass die Elemente im Quellcode aufgeführt sind.

Wie sollte das Verhalten sein, wenn doppelte Elemente für Set.of oder doppelte Schlüssel für Map.of oder Map.ofEntries bereitgestellt werden? Vorausgesetzt, die Elemente werden explizit im Quellcode aufgelistet, ist dies wahrscheinlich ein Programmierfehler. Alternativen wie First-Wins oder Last-Wins scheinen die Fehler wahrscheinlich still zu verdecken. Daher haben wir uns dazu entschieden, dass Duplikate zu einem Fehler gemacht haben, die beste Vorgehensweise. Wenn die Elemente explizit aufgelistet werden, wäre es nett, wenn dies ein Fehler bei der Kompilierung wäre. Die Erkennung von Duplikaten wird jedoch erst zur Laufzeit erkannt. * Das Auslösen einer Ausnahme zu diesem Zeitpunkt ist das Beste, was wir tun können.

* Wenn in der Zukunft alle Argumente konstante Ausdrücke sind oder konstant faltbar sind, kann die Erstellung von Sätzen oder Karten auch zur Kompilierzeit ausgewertet und auch konstant gefaltet werden. Dadurch können möglicherweise Duplikate beim Kompilieren erkannt werden.

Wie sieht es mit dem Anwendungsfall aus, in dem Sie über eine Sammlung von Elementen verfügen und diese deduplizieren möchten? Das ist ein anders Anwendungsfall, und es wird von Set.of und Map.ofEntries nicht gut gehandhabt. Sie müssen zuerst ein Zwischenarray erstellen, was ziemlich umständlich ist:

Set<String> set = Set.of(list.toArray());

Dies wird nicht kompiliert, da list.toArray() einen Object[] zurückgibt. Dadurch wird ein Set<Object> erzeugt, der Set<String> nicht zugewiesen werden kann. Sie möchten, dass toArray Ihnen stattdessen einen String[] gibt:

Set<String> set = Set.of(list.toArray(new String[0]));

Dieser Typ prüft, wirft aber immer noch eine Ausnahme für Duplikate! Eine andere Alternative wurde vorgeschlagen:

Set<String> set = new HashSet<>(list);

Dies funktioniert, aber Sie erhalten eine HashSet zurück, die veränderlich ist und viel mehr Speicherplatz beansprucht als die von Set.of zurückgegebene Menge. Sie können die Elemente über eine HashSet deduplizieren, ein Array daraus erhalten und es dann an Set.of übergeben. Das würde funktionieren, aber bleah.

Glücklicherweise ist dies in Java 10 behoben. Sie können jetzt schreiben:

Set<String> set = Set.copyOf(list);

Dadurch wird eine unveränderbare Menge aus den Elementen der Quellensammlung erstellt, und Duplikate führen nicht eine Ausnahme aus. Stattdessen wird ein beliebiges der Duplikate verwendet. Es gibt ähnliche Methoden List.copyOf und Map.copyOf. Als Bonus überspringen diese Methoden das Erstellen einer Kopie, wenn die Quellensammlung bereits eine unveränderbare Sammlung des richtigen Typs ist.

2
Stuart Marks

Set.of (E ... Elemente)

Der Elementtyp des Ergebnissatzes ist der Komponententyp des Arrays. Die Größe des Satzes entspricht der Länge des Arrays. 

wirft:

IllegalArgumentException - if there are any duplicate elements

Dies ist eindeutig, dass dies keinen doppelten Test durchführt, da Set die Länge des Arrays ist.

Die Methode ist nur hier, um in einer Zeile ein gefülltes Set zu erhalten

Set.of("A","B","C");

Aber Sie müssen vorsichtig mit dem Duplikat sein. (Dadurch werden die varargs einfach durchlaufen und in das neue Set eingefügt.

1
AxelH