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 auch emptyXXX() durch of() und Collections.singleon() durch of(element) ersetzen – allerdings sagen die Collections-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 Adapter asList(…) keine Kopie anlegt, wohingegen List.of(…) zum Aufbau einer neuen internen Datenstruktur führt, die Speicher kostet.
- Falls null-Einträge in der Sammlung sein sollen, dürfen keine ofXXX(…)-Methoden verwendet werden.
- Beim Refactoring könnten Entwickler geneigt sein, existierenden Code mit den Collections-Methode durch die ofXXX(…)-Methoden zu ersetzen. Das kann zum Problem mit serialisierten Daten werden, denn das Serialisierungsformat ist ein anders.
- Bei Set und Map wird ein künstlicher SALT eingesetzt, der die Reihenfolge der Elemente bei jedem JVM-Start immer ändert. Das heißt, der Iterator von of(„a“, „b“, „c“) kann einmal „a“, „b“, „c“ liefern, dann beim nächsten Programmstart „b“, „c“, „a“.