Rheinwerk Computing < openbook >


 
Inhaltsverzeichnis
Materialien
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Arrays und ihre Anwendungen
5 Der Umgang mit Zeichenketten
6 Eigene Klassen schreiben
7 Objektorientierte Beziehungsfragen
8 Ausnahmen müssen sein
9 Geschachtelte Typen
10 Besondere Typen der Java SE
11 Generics<T>
12 Lambda-Ausdrücke und funktionale Programmierung
13 Architektur, Design und angewandte Objektorientierung
14 Java Platform Module System
15 Die Klassenbibliothek
16 Einführung in die nebenläufige Programmierung
17 Einführung in Datenstrukturen und Algorithmen
18 Einführung in grafische Oberflächen
19 Einführung in Dateien und Datenströme
20 Einführung ins Datenbankmanagement mit JDBC
21 Bits und Bytes, Mathematisches und Geld
22 Testen mit JUnit
23 Die Werkzeuge des JDK
A Java SE-Module und Paketübersicht
Stichwortverzeichnis


Download:

- Listings, ca. 2,7 MB


Buch bestellen
Ihre Meinung?



Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom

Einführung, Ausbildung, Praxis
Buch: Java ist auch eine Insel


Java ist auch eine Insel

Pfeil12 Lambda-Ausdrücke und funktionale Programmierung
Pfeil12.1 Funktionale Schnittstellen und Lambda-Ausdrücke
Pfeil12.1.1 Klassen implementieren Schnittstellen
Pfeil12.1.2 Lambda-Ausdrücke implementieren Schnittstellen
Pfeil12.1.3 Funktionale Schnittstellen
Pfeil12.1.4 Der Typ eines Lambda-Ausdrucks ergibt sich durch den Zieltyp
Pfeil12.1.5 Annotation @FunctionalInterface
Pfeil12.1.6 Syntax für Lambda-Ausdrücke
Pfeil12.1.7 Die Umgebung der Lambda-Ausdrücke und Variablenzugriffe
Pfeil12.1.8 Ausnahmen in Lambda-Ausdrücken
Pfeil12.1.9 Klassen mit einer abstrakten Methode als funktionale Schnittstelle? *
Pfeil12.2 Methodenreferenz
Pfeil12.2.1 Motivation
Pfeil12.2.2 Methodenreferenzen mit ::
Pfeil12.2.3 Varianten von Methodenreferenzen
Pfeil12.3 Konstruktorreferenz
Pfeil12.3.1 Parameterlose und parametrisierte Konstruktoren
Pfeil12.3.2 Nützliche vordefinierte Schnittstellen für Konstruktorreferenzen
Pfeil12.4 Funktionale Programmierung
Pfeil12.4.1 Code = Daten
Pfeil12.4.2 Programmierparadigmen: imperativ oder deklarativ
Pfeil12.4.3 Das Wesen der funktionalen Programmierung
Pfeil12.4.4 Funktionale Programmierung und funktionale Programmiersprachen
Pfeil12.4.5 Funktionen höherer Ordnung am Beispiel von Comparator
Pfeil12.4.6 Lambda-Ausdrücke als Abbildungen bzw. Funktionen betrachten
Pfeil12.5 Funktionale Schnittstellen aus dem java.util.function-Paket
Pfeil12.5.1 Blöcke mit Code und die funktionale Schnittstelle Consumer
Pfeil12.5.2 Supplier
Pfeil12.5.3 Prädikate und java.util.function.Predicate
Pfeil12.5.4 Funktionen über die funktionale Schnittstelle java.util.function.Function
Pfeil12.5.5 Ein bisschen Bi …
Pfeil12.5.6 Funktionale Schnittstellen mit Primitiven
Pfeil12.6 Optional ist keine Nullnummer
Pfeil12.6.1 Einsatz von null
Pfeil12.6.2 Der Optional-Typ
Pfeil12.6.3 Primitive optionale Typen
Pfeil12.6.4 Erst mal funktional mit Optional
Pfeil12.6.5 Primitiv-Optionales mit speziellen OptionalXXX-Klassen
Pfeil12.7 Was ist jetzt so funktional?
Pfeil12.8 Zum Weiterlesen
 

Zum Seitenanfang

12.6    Optional ist keine Nullnummer Zur vorigen ÜberschriftZur nächsten Überschrift

Java hat eine besondere Referenz, die Entwicklern die Haare zu Berge stehen lässt und die ein Grund für lange Debug-Stunden ist: die null-Referenz. Eigentlich sagt null nur aus: »belegt, aber nicht initialisiert«. Doch was null so problematisch macht, ist die NullPointerException, die durch referenzierte null-Ausdrücke ausgelöst wird.

[zB]  Beispiel

Entwickler haben vergessen, das Attribut location mit einem Objekt zu initialisieren, sodass setLocation(…) fehlschlagen wird:

class Place {

private Point2D location;

public void setLocation( double longitude, double latitude ) {

location.setLocation( longitude, latitude ); // inline image NullPointerException

}

}
 

Zum Seitenanfang

12.6.1    Einsatz von null Zur vorigen ÜberschriftZur nächsten Überschrift

Fehler dieser Art sind durch Tests relativ leicht aufzuspüren. Aber hier liegt nicht das Problem. Das eigentliche Problem ist, dass Entwickler allzu gerne die typenlose null[ 226 ](null instanceof Typ ist immer false. ) als magischen Sonderwert betrachten, sodass sie neben »nicht initialisiert« noch etwas anderes bedeutet:

  • Erlaubt die API in Argumenten für Methoden/Konstruktoren null, heißt das meistens »nutze einen Default-Wert« oder »nichts gegeben, ignorieren«.

  • In Rückgaben von Methoden steht null oftmals für »nichts gemacht« oder »keine Rückgabe«. Im Gegensatz dazu kodieren andere Methoden wiederum mit der Rückgabe null, dass eine Operation erfolgreich durchlaufen wurde, und würden sonst zum Beispiel Fehlerobjekte zurückgeben.[ 227 ](Zum Glück wird null selten als Fehler-Identifikator genutzt, die Zeiten sind vorbei. Hier sind Ausnahmen die bessere Wahl, denn Fehler sind Ausnahmen im Programm. )

Beispiel 1

Die mit Javadoc dokumentierte Methode getTask(out, fileManager, diagnosticListener, options, classes, compilationUnits) in der Schnittstelle JavaCompiler ist so ein Beispiel:

  • out: »a writer for additional output from the compiler; use system.err if null«

  • fileManager: »a file manager; if null use the compiler’s standard filemanager«

  • diagnosticListener: »a diagnostic listener; if null use the compiler’s default method for reporting diagnostics«

  • options: »compiler options, null means no options«

  • classes: »names of classes to be processed by annotation processing, null means no class names«

  • compilationUnits: »the compilation units to compile, null means no compilation units«

Alle Argumente können null sein: getTask(null, null, null, null, null, null) ist ein korrekter Aufruf. Schön ist die API nicht, und für so lange Parameterlisten gibt es mit dem Builder-Pattern eine feine Alternative. Frei erfunden könnte das so aussehen: new CompilationTask.Builder().out(…).fileManager(…).….build().

Beispiel 2

Der BufferedReader erlaubt das zeilenweise Einlesen aus Datenquellen, und readLine() liefert null, wenn es keine Zeile mehr zu lesen gibt.

Beispiel 3

Viel Irritation gibt es mit der API vom Assoziativspeicher. Eine gewöhnliche HashMap kann als assoziierten Wert null bekommen, doch get(key) liefert auch dann null, wenn es keinen assoziierten Wert gibt. Das führt zu einer Mehrdeutigkeit, da die Rückgabe von get(…) nicht verrät, ob es eine Abbildung auf null gibt oder ob der Schlüssel nicht vorhanden ist.

Map<Integer,String> map = new HashMap<>();

map.put( 0, null );

System.out.println( map.containsKey( 0 ) ); // true

System.out.println( map.containsValue( null ) ); // true

System.out.println( map.get( 0 ) ); // null

System.out.println( map.get( 1 ) ); // null

Kann die Map null-Werte enthalten, muss es immer ein Paar der Art if(map.containsKey(key)), gefolgt von map.get(key), geben. Am besten verzichten Entwickler auf null in Datenstrukturen.

null dokumentieren

Um null in den Griff zu bekommen, sind mehrere Lösungsansätze denkbar. Zunächst müssen wir überlegen, ob wir null überhaupt verwenden wollen. Wenn, dann sollten wir immer in der API-Dokumentation null als potenzielle Rückgabe oder gültige Parameterbelegung dokumentieren. Nur das Problem ist: Wer liest schon die Javadoc? Etwas Expliziteres ist gesucht. Hier bieten sich Annotationen an wie @Nullable und @NonNull, die zwar nicht Teil der Java SE sind, doch im Klassenpfad hinzugenommen werden können und durch statische Analysewerkzeuge ausgewertet werden können – das können auch Eclipse und IntelliJ.

Ist null als Parameter nicht erlaubt, sollte natürlich eine Methode eine Prüfung vornehmen und eine Ausnahme auslösen. Das kann von uns explizit über einen Test veranlasst werden, zum Beispiel mit if ( param == null ) throw new oder indirekt über Objects.requireNonNull(param).

Alternativen zu null

Da null so viele Einsatzfälle hat und das Lesen der Javadoc gerne übersprungen wird, sollte es zu einigen null-Einsätzen Alternativen geben. Manches Mal ist das einfach, etwa wenn die Rückgabe Sammlungen sind. Dann gibt es mit einer leeren Sammlung eine gute Alternative zu null. Das ist ein Spezialfall des sogenannten Null-Object-Patterns und wird in Kapitel 17, »Einführung in Datenstrukturen und Algorithmen«, näher beschrieben.

Fehler, die aufgrund einer NullPointerException entstehen, ließen sich natürlich komplett vermeiden, wenn immer ordentlich auf null-Referenzen getestet würde. Aber gerade die null-Prüfungen werden von Entwicklern gerne vergessen, da ihnen nicht bewusst ist oder weil sie nicht erwarten, dass eine Rückgabe null sein kann. Gewünscht ist ein Programmkonstrukt, bei dem explizit wird, dass ein Wert nicht vorhanden sein kann, sodass nicht null diese Rolle übernehmen muss. Wenn im Code zu lesen ist, dass ein Wert optional ist, also vorhanden sein kann oder nicht, reduziert das Fehler.

[»]  Geschichte

Tony Hoare gilt als »Erfinder« der null-Referenz. Heute bereut er es und nennt die Entscheidung »my billion-dollar mistake«.[ 228 ](Er sagt dazu: »It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.« Unter http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare gibt es ein Video mit ihm und Erklärungen. )

 

Zum Seitenanfang

12.6.2    Der Optional-Typ Zur vorigen ÜberschriftZur nächsten Überschrift

Die Java-Bibliothek bietet eine Art von Container, der ein Element enthalten kann oder nicht. Wenn der Container ein Element enthält, ist es nie null. Dieser Container kann befragt werden, ob er ein Element enthält oder nicht. Eine null als Kennung ist somit überflüssig.

[zB]  Beispiel
Optional<String> opt1 = Optional.of( "Aitazaz Hassan Bangash" );

System.out.println( opt1.isPresent() ); // true

System.out.println( opt1.isEmpty() ); // false

System.out.println( opt1.get() ); // Aitazaz Hassan Bangash

Optional<String> opt2 = Optional.empty();

System.out.println( opt2.isPresent() ); // false

System.out.println( opt2.isEmpty() ); // true

// opt2.get() -> java.util.NoSuchElementException: No value present

Optional<String> opt3 = Optional.ofNullable( "Malala" );

System.out.println( opt3.isPresent() ); // true

System.out.println( opt3.isEmpty() ); // false

System.out.println( opt3.get() ); // Malala

Optional<String> opt4 = Optional.ofNullable( null );

System.out.println( opt4.isPresent() ); // false

// opt4.get() -> java.util.NoSuchElementException: No value present
final class java.util.Optional<T>
  • static <T> Optional<T> empty()

    Liefert ein leeres Optional-Objekt.

  • boolean isPresent()

    Liefert wahr, wenn dieses Optional einen Wert hat, sonst ist wie im Fall von empty() die Rückgabe false.

  • boolean isEmpty()

    Gegenteil von isPresent(). Neu in Java 11.

  • static <T> Optional<T> of(T value)

    Baut ein neues Optional mit einem Wert auf, der nicht null sein darf; andernfalls gibt es eine NullPointerException. null in das Optional hineinzubekommen, geht also nicht.

  • static <T> Optional<T> ofNullable(T value)

    Liefert ein Optional mit dem Wert, wenn dieser ungleich null ist, bei null ist die Rückgabe ein Optional.empty().

  • T get()

    Liefert den Wert. Enthält das Optional keinen Wert, weil es isEmpty() ist, folgt eine NoSuchElementException.

  • T orElse(T other)

    Ist ein Wert isPresent(), liefere den Wert; ist er isEmpty(), liefere other.

  • Stream<T> stream()

    Konvertiere das Optional in den Datentyp Stream. Seit Java 9.

Des Weiteren überschreibt Optional die Methoden equals(…), toString() und hashCode() und ein paar weitere Methoden, die wir uns später anschauen.

[»]  Hinweis

Intern null zu verwenden hat zum Beispiel den Vorteil, dass die Objekte serialisiert werden können. Optional implementiert Serializable nicht, daher sind Optional-Attribute nicht serialisierbar, können also etwa nicht im Fall von Remote-Aufrufen mit RMI übertragen werden. Auch die Abbildung auf XML oder auf Datenbanken ist umständlicher, wenn nicht JavaBean-Properties herangezogen werden, sondern die internen Attribute.

Ehepartner oder nicht?

Optional wird also dazu verwendet, im Code explizit auszudrücken, ob ein Wert vorhanden ist oder nicht. Das gilt auf beiden Seiten: Der Erzeuger muss explizit ofXXX(…) aufrufen und der Nutzer explizit isPresent(), isEmpty() oder get(). Beide Seiten sind sich bewusst, dass sie es mit einem Wert zu tun haben, der optional ist, also existieren kann oder nicht. Wir wollen das in einem Beispiel nutzen, und zwar für eine Person, die einen Ehepartner haben kann:

Listing 12.13    src/main/java/com/tutego/insel/lang/Person.java, Ausschnitt

public class Person {

private Person spouse;

public void setSpouse( Person spouse ) {

this.spouse = Objects.requireNonNull( spouse );

}

public void removeSpouse() {

spouse = null;

}

public Optional<Person> getSpouse() {

return Optional.ofNullable( spouse );

}

}

In diesem Beispiel ist null für die interne Referenz auf den Partner möglich; diese Kodierung soll aber nicht nach außen gelangen. Daher liefert getSpouse() nicht direkt die Referenz, sondern es kommt Optional zum Einsatz und drückt aus, ob eine Person einen Ehepartner hat oder nicht. Auch bei setSpouse(…) akzeptieren wir kein null, denn null-Argumente sollten so weit wie möglich vermieden werden. Ein Optional ist hier nicht angemessen, weil es ein Fehler ist, null zu übergeben. Zusätzlich sollte natürlich die Javadoc an setSpouse(…) dokumentieren, dass ein null-Argument zu einer NullPointerException führt. Daher passt Optional als Parametertyp nicht.

Listing 12.14    src/main/java/com/tutego/insel/lang/OptionalDemo.java, main()

Person heinz = new Person();

System.out.println( heinz.getSpouse().isEmpty() ); // true

Person eva = new Person();

heinz.setSpouse( eva );

System.out.println( heinz.getSpouse().isPresent() ); // true

System.out.println( heinz.getSpouse().get() ); // com/.../Person

heinz.removeSpouse();

System.out.println( heinz.getSpouse().isEmpty() ); // true
 

Zum Seitenanfang

12.6.3    Primitive optionale Typen Zur vorigen ÜberschriftZur nächsten Überschrift

Während Referenzen null sein können und auf diese Weise das Nichtvorhandensein anzeigen, ist das bei primitiven Datentypen nicht so einfach. Wenn eine Methode ein boolean zurückgibt, bleibt neben true und false nicht viel übrig, und ein »nicht zugewiesen« wird dann doch gerne wieder über einen Boolean verpackt und auf null getestet. Gerade bei Ganzzahlen gibt es immer wieder Rückgaben wie –1.[ 229 ](Unter http://docs.oracle.com/en/java/javase/14/docs/api/constant-values.html lassen sich alle Konstantendeklarationen einsehen. ) Das ist bei den folgenden Beispielen der Fall:

  • Wenn bei InputStreams read(…) keine Eingaben mehr kommen, wird –1 zurückgegeben.

  • indexOf(Object) von List liefert –1, wenn das gesuchte Objekt nicht in der Liste ist und folglich auch keine Position vorhanden ist.

  • Bei einer unbekannten Bytelänge einer MIDI-Datei (Typ MidiFileFormat) hat getByteLength() als Rückgabe –1.

Diese magischen Werte sollten vermieden werden, und daher kann auch der optionale Typ wieder erscheinen.

Als generischer Typ kann Optional beliebige Typen kapseln, und primitive Werte könnten in Wrapper verpackt werden. Allerdings bietet Java für drei primitive Typen spezielle Optional-Typen an: OptionalInt, OptionalLong und OptionalDouble:

Optional<T>

OptionalInt

OptionalLong

OptionalDouble

static <T>

Optional<T>

empty()

static

OptionalInt

empty()

static

OptionalLong

empty()

static

OptionalDouble

empty()

T get()

int getAsInt()

long getAsLong()

double getAsDouble()

boolean isPresent()

boolean isEmpty()

static <T>

Optional<T>

of(T value)

static OptionalInt

of(int value)

static OptionalLong

of(long value)

static OptionalDouble

of(double value)

static <T>

Optional<T>

ofNullable(T

value)

nicht übertragbar

T orElse(T other)

int orElse(int other)

long orElse(long other)

double orElse(

double other)

boolean equals(Object obj)

int hashCode()

String toString()

Tabelle 12.15    Methodenvergleich zwischen den vier »OptionalXXX«-Klassen

Die Optional-Methode ofNullable(…) fällt in den primitiven Optional-Klassen natürlich heraus. Die optionalen Typen für die drei primitiven Typen haben insgesamt weniger Methoden, und Tabelle 12.15 oben ist nicht ganz vollständig.

[»]  Best Practice

OptionalXXX-Typen eignen sich hervorragend als Rückgabetyp, sind als Parametertyp denkbar, doch wenig attraktiv für interne Attribute. Intern ist null eine akzeptable Wahl, der »Typ« ist schnell und speicherschonend.

 

Zum Seitenanfang

12.6.4    Erst mal funktional mit Optional Zur vorigen ÜberschriftZur nächsten Überschrift

Neben den vorgestellten Methoden wie ofXXX(…) und isPresent()sowie isEmpty() gibt es weitere, die auf funktionale Schnittstellen zurückgreifen:

final class java.lang.Optional<T>
  • void ifPresent(Consumer<? super T> consumer)

    Repräsentiert das Optional einen Wert, rufe den Consumer mit diesem Wert auf, andernfalls mache nichts.

  • void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)

    Repräsentiert das Optional einen Wert, rufe den Consumer mit diesem Wert auf, andernfalls führe emptyAction aus. Das Runnable muss hier als Typ aus java.lang herhalten, weil es im java.util.function-Paket keine Schnittstelle gibt, die keine Parameter hat und auch keine Rückgabe liefert. Seit Java 9.

  • Optional<T> filter(Predicate<? super T> predicate)

    Enthält das Optional einen Wert und ist das Prädikat predicate auf dem Wert wahr, ist die Rückgabe das eigene Optional (also this), sonst ist die Rückgabe Optional.empty().

  • <U> Optional<U> map(Function<? super T,? extends U> mapper)

    Repräsentiert das Optional einen Wert, dann wende die Funktion an, und verpacke das Ergebnis (wenn es ungleich null ist) wieder in ein Optional. Ist das Optional ohne Wert, dann ist die Rückgabe Optional.empty(). Das gilt genauso, wenn die Funktion null liefert.

  • <U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper)

    Wie map(…), nur dass die Funktion ein Optional statt eines direkten Werts zurückgibt. Liefert die Funktion mapper ein leeres Optional, so ist das Ergebnis von flatMap(…) auch Optional. empty().

  • Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)

    Repräsentiert das Optional einen Wert, so liefere ihn. Ist das Optional leer, beziehe den Wert aus dem anderen Optional. Seit Java 9.

  • T orElseGet(Supplier<? extends T> other)

    Repräsentiert das Optional einen Wert, so liefere ihn; ist das Optional leer, so beziehe den Alternativwert aus dem Supplier.

  • <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)

    Repräsentiert das Optional einen Wert, so liefere ihn, andernfalls hole mit Supplier das Ausnahmeobjekt und löse es aus.

  • T orElseThrow()

    Wie get(). Seit Java 10.

[zB]  Beispiel

Wenn das Optional keinen Wert hat, soll eine NullPointerException statt der NoSuchElementException ausgelöst werden.

String s = optionalString.orElseThrow( NullPointerException::new );

Beispiel für eine NullPointerException-sichere Kaskadierung von Aufrufen mit Optional

Die beiden XXXmap(…)-Methoden sind besonders interessant und ermöglichen einen ganz neuen Programmierstil. Warum das so ist, soll ein Beispiel zeigen.

Der folgende Zweizeiler gibt auf meinem System »MICROSOFT KERNELDEBUGGER-NETZWERKADAPTER« aus:

String s = NetworkInterface.getByIndex( 2 ).getDisplayName().toUpperCase();

System.out.println( s );

Allerdings ist der Programmcode alles andere als gut, denn NetworkInterface.getByIndex(int) kann null zurückgeben und getDisplayName() auch. Um ohne eine NullPointerException die Klippen zu umschiffen, müssen wir Folgendes schreiben:

NetworkInterface networkInterface = NetworkInterface.getByIndex( 2 );

if ( networkInterface != null ) {

String displayName = networkInterface.getDisplayName();

if ( displayName != null )

System.out.println( displayName.toUpperCase() );

}

Von der Eleganz des Zweizeilers ist nicht mehr viel übrig geblieben. Integrieren wir Optional (das ja eigentlich ein toller Rückgabetyp für getByIndex() und getDisplayName() wäre):

Optional<NetworkInterface> networkInterface = Optional.ofNullable( NetworkInterface.getByIndex( 2 ) );

if ( networkInterface.isPresent() ) {

Optional<String> name = Optional.ofNullable( networkInterface.get().getDisplayName() );

if ( name.isPresent() )

System.out.println( name.get().toUpperCase() );

}

Mit Optional wird es nicht sofort besser, doch statt if können wir einen Lambda-Ausdruck nehmen und bei ifPresent(…) einsetzen:

Optional<NetworkInterface> networkInterface = Optional.ofNullable( NetworkInterface.getByIndex( 2 ) );

networkInterface.ifPresent( ni -> {

Optional<String> displayName = Optional.ofNullable( ni.getDisplayName() );

displayName.ifPresent( name -> {

System.out.println( name.toUpperCase() );

} );

} );

Wenn wir die lokalen Variablen networkInterface und displayName entfernen, landen wir bei:

Optional.ofNullable( NetworkInterface.getByIndex( 2 ) ).ifPresent( ni -> {

Optional.ofNullable( ni.getDisplayName() ).ifPresent( name -> {

System.out.println( name.toUpperCase() );

} );

} );

Von der Struktur her ist das mit der if-Abfrage identisch und über die Einrückungen auch zu erkennen. Fallunterscheidungen mit Optional und ifPresent(…) umzuschreiben, bringt keinen Vorteil.

In Fallunterscheidungen zu denken, hilft hier nicht weiter. Was wir uns bei NetworkInterface.getByIndex( 2 ).getDisplayName().toUpperCase() vor Augen halten müssen, ist eine Kette von Abbildungen. NetworkInterface.getByIndex(int) bildet auf NetworkInterface ab, getDisplayName() von NetworkInterface bildet auf String ab, und toUpperCase() bildet von einem String auf einen anderen String ab. Wir verketten drei Abbildungen und müssten Folgendes ausdrücken können: Wenn eine Abbildung fehlschlägt, dann höre mit der Abbildung auf. Und genau hier kommen Optional und map(…) ins Spiel. In Code sieht das so aus:

Optional<String> s = Optional.ofNullable( NetworkInterface.getByIndex( 2 ) )

.map( ni -> ni.getDisplayName() )

.map( name -> name.toUpperCase() );

s.ifPresent( System.out::println );

Die Klasse Optional hilft uns bei zwei Dingen: Erstens wird map(…) beim Empfangen einer null-Referenz auf ein Optional.empty() abbilden, und zweitens ist das Verketten von leeren Optionals kein Problem: Es passiert einfach nichts – Optional.empty().map(…) führt nichts aus, und die Rückgabe ist einfach nur ein leeres Optional. Am Ende der Kette steht nicht mehr String (wie am Anfang des Beispiels), sondern Optional<String>.

Umgeschrieben mit Methodenreferenzen und weiter verkürzt, ist der Code sehr gut lesbar und sicher vor einer NullPointerException:

Optional.ofNullable( NetworkInterface.getByIndex( 2 ) )

.map( NetworkInterface::getDisplayName )

.map( String::toUpperCase )

.ifPresent( System.out::println );

Die Logik kommt ohne externe Fallunterscheidungen aus und arbeitet nur mit optionalen Abbildungen. Das ist ein schönes Beispiel für funktionale Programmierung.

 

Zum Seitenanfang

12.6.5    Primitiv-Optionales mit speziellen OptionalXXX-Klassen Zur vorigen ÜberschriftZur nächsten Überschrift

Die eigentliche Optional-Klasse ist generisch und kapselt jeden Referenztyp. Auch für die primitiven Typen int, long und double gibt es in den drei speziellen Klassen OptionalInt, OptionalLong und OptionalDouble Methoden zur funktionalen Programmierung. Stellen wir in Tabelle 12.15 die Methoden der vier OptionalXXX-Klassen gegenüber:

Optional<T>

OptionalInt

OptionalLong

OptionalDouble

static <T>

Optional<T>

empty()

static

OptionalInt

empty()

static

OptionalLong

empty()

static

OptionalDouble

empty()

T get()

int getAsInt()

long getAsLong()

double

getAsDouble()

boolean isPresent()

boolean isEmpty()

static <T>

Optional<T> of(T)

static OptionalInt

of(int)

static OptionalLong

of(long)

static OptionalDouble

of(double)

static <T>

Optional<T>

ofNullable(T)

nicht übertragbar

T orElse(T)

int orElse(int)

long orElse(long)

double orElse (double)

Stream<T> stream()

IntStream stream()

LongStream  stream()

DoubleStream  stream()

boolean equals(Object)

int hashCode()

String toString()

void ifPresent(

Consumer<? super T>)

void ifPresent(

IntConsumer)

void ifPresent(

LongConsumer)

void ifPresent(

DoubleConsumer)

Void ifPresentOrElse( Consumer<? super T>, Runnable)

void ifPresentOrElse( IntConsumer,  Runnable)

void ifPresentOrElse( LongConsumer,  Runnable)

void ifPresentOrElse( DoubleConsumer,  Runnable)

T orElseGet( Supplier<? extends T>)

int orElseGet( IntSupplier)

long orElseGet( LongSupplier)

double orElseGet( DoubleSupplier)

<X extends Throwable> T orElseThrow(Supplier<? extends X>)

<X extends Throwable> int orElseThrow(Supplier<? extends X>)

<X extends Throwable> long orElseThrow(Supplier<? extends X>)

<X extends Throwable> double

orElseThrow( Supplier<? extends X>)

Optional<T> filter(Predicate<? super T>)

nicht vorhanden

<U> Optional<U>

flatMap(Function

<? super T,Optional<U>>)

<U> Optional<U> map(Function<? super T, ? extends U>)

Tabelle 12.16    Vergleich von »Optional« mit den primitiven »OptionalXXX«-Klassen, Parametervariablen entfernt

Die Methoden ifPresentOrElse(…) und stream() gibt es erst ab Java 9.

[+]  Tipp

Dass die mapXXX(…)-Methoden fehlen, ist unpraktisch, doch wenn Transformationen gewünscht sind, lässt sich mit stream() ein Strom erfragen und lassen sich dort die mapXXX(…)-Methoden aufrufen. Beispiel: Ziehe die Wurzel aus einer »optionalen Zahl«.

OptionalDouble sqrt = OptionalDouble.of( 16 ).stream().map( Math::sqrt ).findFirst();

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java ist auch eine Insel Java ist auch eine Insel

Jetzt Buch bestellen


 Buchempfehlungen
Zum Rheinwerk-Shop: Captain CiaoCiao erobert Java

Captain CiaoCiao erobert Java




Zum Rheinwerk-Shop: Java SE 9 Standard-Bibliothek

Java SE 9 Standard-Bibliothek




Zum Rheinwerk-Shop: Algorithmen in Java

Algorithmen in Java




Zum Rheinwerk-Shop: Objektorientierte Programmierung

Objektorientierte Programmierung




 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2021

Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.

Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 

[Rheinwerk Computing]



Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de



Cookie-Einstellungen ändern