Statische ofXXX(…)-Methoden zum Aufbau unveränderbarer Set-, List-, Map-Datenstrukturen
In Java 9 sind echte immutable Datenstrukturen dazugekommen, die sich über statische ofXXX(…)-Methoden der Schnittstellen List, Set und Map aufbauen lassen. Jede versuchte Änderung an den Datenstrukturen führt zu einer UnsupportedOperationException. Damit eigenen sie sich hervorragend für konstante Sammlungen, die problemlos herumgereicht können.
Aus Performance-Gründen sind die of(…)-Methoden überladen, das ändert aber nichts an ihrem Aufrufvarianten. null-Elemente sind grundsätzlich verboten und führen zu einer NullPointerException.
interface java.util.List<E>
extends Collection<E>
static <E> List<E> of(E… elements)Erzeugt eine neue immutable Liste aus den Elementen. Vor dem praktischen of(…) wurden Listen in der Regel mit Array.asList(…) aufgebaut. Doch die sind nicht immutable und schreiben auf das Feld durch.
interface java.util.Set<E>
extends Collection<E>
static <E> Set<E> of(E… elements)Erzeugt eine Menge aus den gegebenen Elementen. Doppelte Einträge sind verboten und führen zu einer IllegalArgumentException. Der Versuch, schon vorhandene Elemente in eine „normale“ HashSet oder TreeSet hinzuzufügen, ist aber völlig legitim. Wie die Implementierung der Menge genau ist, ist verborgen.
Beispiel
Zeige an, welche Superhelden in einem String Liste vorkommen:
Set<String> heros = Set.of( "Batman", "Spider-Man", "Hellboy" );
new Scanner( "Batman trifft auf Superman" )
.tokens().filter( heros::contains )
.forEach( System.out::println ); // Batman
Zum Aufbau von Assoziationsspeichern gibt es zwei Varianten. Einmal über die of(…)-Methode, die Schlüssel und Wert einfach hintereinander aufnimmt und einmal mit ofEntries(…) über ein Vararg von Entry-Objekten. Eine neue statische Methode hilft, diese einfach aufzubauen:
interface java.util.Map<K,V>
- static <K, V> Map<K, V> of() {
static <K, V> Map<K, V> of(K k1, V v1)static <K, V> Map<K, V> of(K k1, V v1 … )static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9, K k10, V v10)static <K, V> Map<K, V> ofEntries(Entry<? extends K, ? extends V>… entries)static <K, V> Entry<K, V> entry(K k, V v)
Beispiel
Baue eine Map mit Java-Versionen und deren Erscheinungsdaten auf und gib sie aus:
Map<String, LocalDate> map =
Map.ofEntries( Map.entry( „JDK 1.0“, LocalDate.of( 1996, Month.JANUARY, 23 ) ),
Map.entry( "JDK 1.1", LocalDate.of( 1997, Month.FEBRUARY, 19 ) ) );
map.forEach( (k, v) -> System.out.println( k + "=" + v ) );
Best-Practise und Weise Worte
Die neuen ofXXX(…)-Methoden sind eine Bereicherung, aber auch mit Vorsicht einzusetzen – die alten API-Methoden werden dadurch nicht langweilig:
- Da die
of(…)-Methoden überladen sind lässt sich prinzipiell auchemptyXXX()durchof()undCollections.singleon()durchof(element)ersetzen – allerdings sagen dieCollections-Methodennamen gut aus, was hier passiert und sind vielleicht expliziter. - Auf einem existierenen Array hat
Arrays.asList(…)zwar den Nachteil, dass die Array-Elemente ausgetauscht werden können, allerdings ist der Speicherbedarf minimal, da der AdapterasList(…)keine Kopie anlegt, wohingegenList.of(…)zum Aufbau einer neuen internen Datenstruktur führt, die Speicher kostet. - Falls
null-Einträge in der Sammlung sein sollen, dürfen keineofXXX(…)-Methoden verwendet werden. - Beim Refactoring könnten Entwickler geneigt sein, existierenden Code mit den
Collections-Methode durch dieofXXX(…)-Methoden zu ersetzen. Das kann zum Problem mit serialisierten Daten werden, denn das Serialisierungsformat ist ein anders. - Bei
SetundMapwird ein künstlicher SALT eingesetzt, der die Reihenfolge der Elemente bei jedem JVM-Start immer ändert. Das heißt, derIteratorvon of(„a“, „b“, „c“) kann einmal „a“, „b“, „c“ liefern, dann beim nächsten Programmstart „b“, „c“, „a“.