Java 8 bekommt eine Optional Klasse und die beiden XXXmap(…)-Methoden sind besonders interessant. Sie ermöglichen einen ganz neuen Programmierstil. Warum 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 um die Klippen zu schiffen müssen wir 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 geblieben. Integrieren wir Optional (was ja eigentlich ein toller Rückgabetyp für getByIndex() und getDisplayName():
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 ein Lambda-Ausdruck einsetzen 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.get().toUpperCase() );
} );
} );
Wenn wir nun die lokale Variablen entfernen, kommen wir aus bei:
Optional.ofNullable( NetworkInterface.getByIndex( 2 ) ).ifPresent( ni -> {
Optional.ofNullable( ni.getDisplayName() ).ifPresent( name -> {
System.out.println( name.get().toUpperCase() );
} );
} );
Von der Struktur ist das mit der if-Afrage identisch und über die Einrückungen auch zu erkennen. Fallunterscheidungen mit Optional und ifPresent(…) umzuschreiben bringt also 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 also drei Abbildungen und müssten ausdrücken können: Wenn eine Abbildung fehlschlägt, dann höre mit der Abbildung auf. Und genau hier kommt Optional und map(…) ins Spiel. In Code:
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: Erstes 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.
Umgeschrieben mit Methoden-Referenzen und weiter verkürzt ist das Code sehr gut lesbar.
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.