Java 8 und java.util.Optional<T>

von Hubert Schmid vom 2013-12-22

Java 8 führt die generische Klasse java.util.Optional<T> ein. Auf den ersten Blick erscheint dieser Schritt merkwürdig. Denn mit null existiert bereits ein Mechanismus für die Repräsentation nicht-vorhandener Objekte. Doch Optional<T> bietet einige interessante Möglichkeiten – und hilft möglicherweise auch bei der NullPointerException-Problematik.

Die Idee ist einfach: An Schnittstellen wird Optional<T> an Stelle von Referenzen verwendet, die null sein können. Alle anderen Schnittstellen verwenden weiterhin normale Referenzen. Konsequent umgesetzt würde null also weder über- noch zurückgegeben werden, und den NullPointerExceptions wäre die Grundlage entzogen.

Ein paar Beispiele sollen den Unterschied verdeutlichen. Beginnen werde ich mit einer Variante der get-Methode von java.util.Map. Die lookup-Methode gibt im Gegensatz zur Ersteren eine Optional-Instanz zurück, die abhängig von der Existenz einer Objektreferenz in der Map entweder empty oder present ist. Die Klasse Optional bietet dafür die statische Methode ofNullable an.

import java.util.Optional; public interface Map<K, V> { // ... default Optional<V> lookup(Object key) { return Optional.ofNullable(get(key)); } }

Der erste Vorteil ist offensichtlich: Die Signatur der Methode macht deutlich, dass der Rückgabewert optional ist. Auch der zweite Vorteil ist klar: Solche optionalen Referenzen können nicht mehr einfach einer Methode übergeben werden, für die null nicht zulässig ist. Der folgende Code lässt sich beispielsweise nicht übersetzen, und verhindert dadurch eine potentielle NullPointerException.

Map<String, String> dateFormatByCountyCode = ...; long now = System.currentTimeMillis(); System.out.printf(dateFormatByCountyCode.lookup("DE"), now); // ERROR

Stattdessen muss die benötigte Referenz explizit aus der Optional-Instanz geholt werden. Ist man sich sicher, dass tatsächlich ein Objekt referenziert wird, so bietet sich die get-Methode an. Diese wirft eine NoSuchElementException, falls man sich getäuscht hat. Auch das ist ein Vorteil gegenüber der Verwendung von null, denn nun wird die Ausnahme an der Stelle des Fehlers geworfen – anstatt von irgendeiner Stelle der Standardbibliothek, die mit dem eigentlichen Problem nichts zu tun hat.

String format = dateFormatByCountyCode.lookup("DE").get(); System.out.printf(format, now);

Darüber hinaus bietet die Optional-Klasse einige Methoden für den Umgang mit leeren Referenzen. Im folgenden Listing wird beispielsweise die orElse-Methode verwendet. Mit ihr wird auf einen Default-Wert zurückgegriffen, falls kein passender Eintrag in der Map existiert.

String format = dateFormatByCountyCode.lookup("DE").orElse("%tY-%<tm-%<td"); System.out.printf(format, now);

Die Vorteile von Optional<T> gegenüber der Verwendung von null lassen sich also in drei wesentlichen Punkten zusammenfassen:

Zum letztem Punkt habe ich bisher nur die orElse-Methode erwähnt. Darüber hinaus bietet die Klasse Optional noch die Methoden orElseGet, orElseThrow, filter, map, flatMap und ifPresent an. Die Verwendung der beiden Wichtigsten ist im folgenden Listing zu sehen.

public static String getDisplayName(Track track) { return String.format("%s: %s (%s)", track.getArtist() .orElseGet(() -> track.getAlbumArtist().get()), track.getTitle(), track.getGenre() .map(Genre::getDisplayName) .orElse("unknown")); }

Fazit: Optional kann sowohl helfen, Code zu vereinfachen, als auch NullPointerExceptions zu vermeiden. Zumindest in der Theorie – in der Praxis funktioniert dieser Ansatz nur, wenn Optional konsequent im eigenen Code sowie allen verwendeten Bibliotheken eingesetzt wird. Leider scheitert man bereits bei der Standardbibliothek: Denn Java 8 verwendet die neue Klasse ausschließlich in der Stream-API. Alle anderen Klassen bleiben – vermutlich aus Kompatibilitätsgründen – unangetastet. Das heißt: Mit NullPointerExceptions müssen Java-Entwickler noch eine Weile leben.