Test the best

Best of Java 10: API-Neuerungen – Immutable Collections

Michael Inden

© Shutterstock / Profit_Image

Nur rund sechs Monate nach dem Erscheinen des umfangreichen und bedeutsamen Updates Java 9 erblickte Java 10 im März 2018 das Licht der Welt. Da Java 11 gerade erschienen ist, ergreift Michael Inden die Gelegenheit und wirft in dieser Artikelserie einen Blick auf die wichtigsten Features des Vorgängers.

Michael Inden hat bereits die Highlights von Java 9 in seiner Artikelserie Java 9 – Eine Einführung vorgestellt. Die vorliegende Artikelserie setzt sich aus Texten zusammen, die auch in seinem neuen Buch zu Java 10/11, das beim dpunkt.verlag erscheinen wird.

Im ersten Teil der Serie ging es um die Syntax-Erweiterungen in Java 10. In diesem Teil gehe ich auf das Erzeugen unveränderlicher Kopien von Listen, Sets und Maps ein. Außerdem zeige ich eine Erweiterung im Stream API bei den Kollektoren, die nun ebenfalls unveränderliche Ergebnisdatenstrukturen liefern können.

Unveränderliche Kopien von Collections

Bevor wir auf die konkrete Neuerung zum Erzeugen unveränderlicher Kopien in Java 10 schauen, wollen wir zunächst einen Blick zurück wagen. Mit Java 9 wurden sogenannte Collection Factory Methods eingeführt, die es erlauben, auf lesbare Art unveränderliche Collections zu erzeugen. Dazu dienen verschiedene statische of()-Methoden bzw. für Maps eine ofEntries()-Methode:

private Set<String> createImmutableSetJdk9Style() 
{ 
    return Set.of("Tim", "Tom", "Mike"); 
}

Alternativ konnte man, basierend auf einer festgelegten Wertefolge, eine korrespondierende unveränderliche Liste erzeugen – jedoch kein Set und keine Map.

final List<String> names = Arrays.asList("Tim", "Tom", "Mike");

In keinem Fall war es möglich, von einem bereits existierenden Container eine unveränderliche Kopie zu erzeugen. Deshalb wurde für Kopien oftmals folgendes Idiom eines Konstruktoraufrufs genutzt:

final List<String> newCopyOfCollection = new ArrayList<>(names);

Basierend auf den Eigenschaften einer ArrayList<E> ist die entstehende Kopie allerdings nicht unveränderlich – nicht einmal dann, wenn es die ursprüngliche Datenstruktur war. Auch der bis einschließlich Java 8 verfügbare „Umweg“ über die Utility-Funktionalität in Form der überladenen Methoden unmodifableXyz() (Xyz = List, Set, Map) führt nicht zum gewünschten Ergebnis: Es entsteht dadurch ein Wrapper um die ursprüngliche Datenstruktur, jedoch keine wirklich unveränderliche Kopie. Zudem ist recht viel Schreibaufwand nötig:

private Set<String> createImmutableSetOldStyle() 
{ 
     final Set<String> names = new HashSet<>(); 
     names.add("Tim"); 
     names.add("Tom"); 
     names.add("Mike"); 

     return Collections.unmodifiableSet(names); 
}

Erweiterung in Java 10

Für die Fälle, in denen man eine unveränderliche Kopie benötigt, macht die in Java 10 neu eingeführte Methode copyOf() das Leben deutlich einfacher, wie es folgender Aufruf eindrucksvoll zeigt:

final List<String> newImmutableCopy = List.copyOf(names);

Selbstverständlich gibt es Analogien für Sets und Maps, nämlich Set.copyOf(origSet) und Map.copyOf(origMap).

Beispiel

Betrachten wir die neue Kopierfunktionalität für die jeweiligen Datenstrukturen:

final var names = List.of("Tim", "Tom", "Mike", "Peter");
final List<String> copyOfNames = List.copyOf(names);
System.out.println("copyOfList: " +copyOfNames.getClass());
	
final var colors = Set.of("Red", "Green", "Blue");
final Set<String> copyOfColors = Set.copyOf(colors);
System.out.println("copyOfSet:  " + copyOfColors.getClass());
	
final var personAgeMapping = Map.of("Tim", 47L, "Tom", 12L, "Mike", 47L, "Max", 25L
final Map<String, Long> copyOfMap = 
	Map.copyOf(personAgeMapping); System.out.println("copyOfMap:  " + copyOfMap.getClass()); 

Führt man die obigen Anweisungen aus, so kommt es zu folgenden Ausgaben, die zeigen, dass hier die mit Java 9 eingeführten Immutable-Collection-Klassen erzeugt werden:

copyOfList: class java.util.ImmutableCollections$ListN
copyOfSet:  class java.util.ImmutableCollections$SetN
copyOfMap:  class java.util.ImmutableCollections$MapN

Immutable Collections aus Streams erzeugen

Das Stream API war schon mit Java 8 recht umfangreich und wurde mit Java 9 erweitert. Allerdings ließen sich bislang keine unveränderlichen Datenstrukturen erzeugen. Dies wird seit Java 10 durch die Methoden toUnmodifiableList(), toUnmodifiableSet() und toUnmodifiableMap() der Klasse java.util.stream.Collectors möglich.

Unveränderliche Listen und Sets

Einführend schauen wir auf ein Beispiel für unveränderliche Listen und Sets:

final var names = new ArrayList<>(List.of("Tim", "Tom", "Mike", "Peter", "Tom", Tim"));
	
final var immutableNames = names.stream().				                   
	collect(Collectors.toUnmodifiableList());
System.out.println("immutableNames type: " + immutableNames.getClass());

final var uniqueImmutableNames = names.stream().
	collect(Collectors.toUnmodifiableSet());

System.out.println("uniqueImmutableNames content: " + uniqueImmutableNames);

System.out.println("uniqueImmutableNames type: " + uniqueImmutableNames.getClass());

Diese Zeilen geben Folgendes aus:

immutableNames type: class java.util.ImmutableCollections$ListN
uniqueImmutableNames content: [Peter, Mike, Tim, Tom]
uniqueImmutableNames type: class java.util.ImmutableCollections$SetN

Interessanterweise verhält sich hier der Kollektor anders als die Methode of() aus dem Interface Set, diese würde nämlich bei Duplikaten eine Exception auslösen. Das geschieht hier für die Duplikation von «Tim» und «Tom» jedoch nicht.

Unveränderliche Maps

Kommen wir schließlich noch zur Erzeugung unveränderlicher Maps. Als Beispiel hierzu bereiten wir folgendermaßen aus den vorherigen Daten ein Histogramm auf, das die Häufigkeit der jeweiligen Namen repräsentiert.

final var nameCount = names.stream().
		collect(Collectors.toUnmodifiableMap(key -> key,
	value -> 1L, (count, inc) -> count + inc));
System.out.println("content: " + nameCount);
System.out.println("type:    " + nameCount.getClass());

Damit erhalten wir folgende Ausgaben:

	content: {Peter=1, Mike=1, Tim=2, Tom=2}
type:	class java.util.ImmutableCollections$MapN

Wie funktioniert die Magie? Schauen wir dazu auf die Methodensignatur:

toUnmodifiableMap(Function<? super T, ? extends K> keyMapper,
	Function<? super T, ? extends U> valueMapper,
	BinaryOperator<U> mergeFunction) 

Die zuvor gezeigten Lambdas machen eine Identitätsabbildung für die Schlüssel, setzen den Startwert auf 1 und vereinigen dann die Werte über eine sogenannte Merge-Funktion, die definiert, wie Kollisionen bei gleichem Key aufgelöst werden.

Im nächsten Teil stelle ich eine kleine, aber feine Erweiterung in der Klasse java.util.Optional<T> vor.

Verwandte Themen:

Geschrieben von
Michael Inden
Michael Inden
Dipl.-Inform. Michael Inden ist Oracle-zertifizierter Java-Entwickler für JDK 6. Nach seinem Studium in Oldenburg war er lange Zeit als Softwareentwickler und -architekt bei verschiedenen internationalen Firmen tätig und arbeitet derzeit als Teamleiter Softwareentwicklung in Zürich. Michael Inden hat rund 20 Jahre Erfahrung beim Entwurf komplexer Softwaresysteme gesammelt, an diversen Fortbildungen und an mehreren Java-One-Konferenzen in San Francisco teilgenommen. Sein Wissen gibt er gerne als Trainer in Schulungen und auf Konferenzen weiter. Sein besonderes Interesse gilt dem Design qualitativ hochwertiger Applikationen mit ergonomischen, grafischen Oberflächen sowie dem Coaching von Kollegen.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: