java.util.Optional ist keine Nullnummer

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

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 );  // BANG! NullPointerException
   }
 }

Einsatz von null

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[1] als magischen Sonderwert sehen, 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ückgegeben.[2]

Beispiel 1

Die mit Javadoc dokumentiere 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 besser wäre sie wohl mit einem Builder-Pattern gelöst.

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.

Da null so viele Einsatzfälle hat und das Lesen der API-Dokumentation 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 so genannten Null-Object-Patterns.

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 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 lesbar 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“.[3]

Optional-Typ

Die Java-Bibliothek bietet eine Art 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.

Beispiel

Optional<String> opt1 = Optional.of( "Aitazaz Hassan Bangash" );
 System.out.println( opt1.isPresent() );   // true
 System.out.println( opt1.get() );         // Aitazaz Hassan Bangash
 Optional<String> opt2 = Optional.empty();
 System.out.println( opt2.isPresent() );   // false
 // opt2.get() -> java.util.NoSuchElementException: No value present
 Optional<String> opt3 = Optional.ofNullable( "Malala" );
 System.out.println( opt3.isPresent() );   // true
 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.
  • booleanisPresent()
    Liefert wahr, wenn dieses Optional einen Wert hat, sonst ist wie im Fall von empty() die Rückgabe false.
  • static<T>Optional<T>of(Tvalue)
    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(Tvalue)
    Liefert ein Optional mit dem Wert, wenn dieser ungleich null ist, bei null ist die Rückgabe ein empty().
  • Tget()
    Liefert den Wert. Enthält das Optional keinen Wert, weil kein Wert isPresent() ist, folgt eine NoSuchElementException.
  • TorElse(Tother)
    Ist ein Wert isPresent(), liefere den Wert. Ist das Optional leer, liefere other.
  • Stream<T> stream()
    Konvertiere das Optional in den Datentyp Stream. Neu in Java 9.

Des Weiteren überschreibt Optional die Methoden equals(…), toString() und hashCode() – 0, wenn kein Wert gegeben ist, sonst Hashcode vom Element – 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() 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:

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.

Person heinz = new Person();
 System.out.println( heinz.getSpouse().isPresent() ); // false
 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().isPresent() ); // false

Primitive optionale Typen

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.[4] 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, OptionalDouble:

Optional<T> OptionalInt OptionalLong OptionalDouble
static <T>
Optional<T>
empty()
static
OptionalInt
empty()
static
OptionalLong
empty()
static
Optional-Double
empty()
T get() int getAsInt() long getAsLong() double getAsDouble()
boolean isPresent()
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: Methodenvergleich zwischen den vier OptionalXXX-Klassen

Die Optional-Methode ofNullable(…) fällt in den primitiven Optional-Klassen natürlich raus. Die optionalen Typen für die drei primitiven Typen haben insgesamt weniger Methoden, und die obere Tabelle ist nicht ganz vollständig. Wir kommen im Rahmen der funktionalen Programmierung in Java noch auf die verbleibenden Methoden wie isPresent(…) zurück.

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.

Erstmal funktional mit Optional

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

final class java.lang.Optional<T>

  • voidifPresent(Consumer<?superT>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 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. Neu in Java 9.
  • Optional<T>filter(Predicate<?superT>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 empty().
  • <U>Optional<U>map(Function<?superT,?extendsU>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 empty(), genauso, wenn die Funktion null liefert.
  • <U>Optional<U>flatMap(Function<?superT,Optional<U>>mapper)
    Wie map(…), nur dass die Funktion ein Optional statt eines direkten Werts gibt. Liefert die Funktion mapper ein leeres Optional, so ist das Ergebnis von flatMap(…) auch 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. Neu in Java 9.
  • TorElseGet(Supplier<?extendsT>other)
    Repräsentiert das Optional einen Wert, so liefere ihn; ist das Optional leer, so beziehe den Alternativwert aus dem Supplier.
  • <XextendsThrowable>TorElseThrow(Supplier<?extendsX>exceptionSupplier)
    Repräsentiert das Optional einen Wert, so liefere ihn, andernfalls hole mit Supplier das Ausnahme-Objekt, und löse es aus.

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 NullPointerException-sichere Kaskadierung von Aufrufen mit Optional

Die beiden XXXmap(…)-Methoden sind besonders interessant und 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() 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 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:

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 Null-Pointer-Exception-sicher:

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.

Primitiv-Optionales mit

Die eigentliche Optional-Klasse ist generisch und kapselt jeden Referenztyp. Auch für die primitiven Typen int, long und double gibt es in drei speziellen Klassen OptionalInt, OptionalLong, OptionalDouble Methoden zur funktionalen Programmierung. Stellen wir 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()
static <T>
Optional<T> of(T value)
static OptionalInt
of(int value)
static OptionalLong
of(long value)
static Optional
Double
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)
Stream<T> stream() IntStream stream() LongStream stream() DoubleStream stream()
boolean equals(Object obj)
int hashCode()
String toString()
void ifPresent(
Consumer<? super T> consumer)
void ifPresent(
IntConsumer
consumer)
void ifPresent(
LongConsumer
consumer)
void ifPresent(
DoubleConsumer
consumer)
void ifPresentOrElse( Consumer<? super T> action, Runnable emptyAction) void ifPresentOrElse( IntConsumer action, Runnable emptyAction) void ifPresentOrElse( LongConsumer action, Runnable emptyAction) void ifPresentOrElse( DoubleConsumer action, Runnable emptyAction)
T orElseGet( Supplier<? extends T> other) int orElseGet( IntSupplier other) long orElseGet( LongSupplier other) double orElseGet( DoubleSupplier other)
<X extends Throwable> T orElseThrow( Supplier<? extends X> exceptionSupplier) <X extends Throwable> int orElseThrow( Supplier<? extends X> exceptionSupplier) <X extends Throwable> long orElseThrow( Supplier<? extends X> exceptionSupplier) <X extends Throwable> double
orElseThrow( Supplier<? extends X>
exceptionSupplier)
Optional<T> filter(Predicate<? super T> predicate) nicht vorhanden
<U> Optional<U>
flatMap(Function
<? super T,Optional<U>> mapper)
<U> Optional<U> map(Function<? super T,? extends U> mapper)

Tabelle 1.13: Vergleich von Optional mit den primitiven OptionalXXX-Klassen

[1]      null instanceof Typ ist immer false .

[2] 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.

[3]      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.

[4] Unter http://docs.oracle.com/javase/8/docs/api/constant-values.html lassen sich alle Konstantendeklarationen einsehen.

Konstruktorreferenzen

Um ein Objekt aufzubauen, nutzen wir das Schlüsselwort new. Das führt zum Aufruf eines Konstruktors, dem sich optional Argumente übergeben lassen. Die Java-API deklariert aber auch Typen, von denen sich keine direkten Exemplare mit new aufbauen lassen. Stattdessen gibt es Erzeuger, deren Aufgabe es ist, Objekte aufzubauen. Die Erzeuger können statische oder auch nichtstatische Methoden sein:

Konstruktor … … erzeugt: Erzeuger … … baut:
new Integer( „1“ ) Integer Integer.valueOf( „1“ ) Integer
new File( „dir“ ) File Paths.get( „dir“ ) Path
new BigInteger( val ) BigInteger BigInteger.valueOf( val ) BigInteger

Tabelle 1.4: Beispiele für Konstruktoren und Erzeuger-Methoden

Beide, Konstruktoren und Erzeuger, lassen sich als spezielle Funktionen sehen, die von einem Typ in einen anderen Typ konvertieren. Damit eignen sie sich perfekt für Transformationen, und in einem Beispiel haben wir das schon eingesetzt:

Arrays.stream( words )
       . …
       .map( Integer::parseInt )
       . …

Integer.parseInt(string) ist eine Methode, die sich einfach mit einer Methodenreferenz fassen lässt, und zwar als Integer::parseInt. Aber was ist mit Konstruktoren? Auch sie transformieren! Statt Integer.parseInt(string) hätte ja auch new Integer(string) eingesetzt werden können.

Wo Methodenreferenzen statische Methoden und Objektmethoden angeben können, bieten Konstruktorreferenzen die Möglichkeit, Konstruktoren anzugeben, sodass diese als Erzeuger an anderer Stelle übergeben werden können. Damit lassen sich elegant Konstruktoren als Erzeuger angeben, und zwar auch von einer Klasse, die nicht über Erzeugermethoden verfügt. Wie auch bei Methodenreferenzen spielt eine funktionale Schnittstelle eine entscheidende Rolle, doch dieses Mal ist es die Methode der funktionalen Schnittstelle, die mit ihrem Aufruf zum Konstruktoraufruf führt. Wo syntaktisch bei Methodenreferenzen rechts vom Doppelpunkt ein Methodenname steht, ist dies bei Konstruktorreferenzen ein new.[1] Also ergibt sich alternativ zu

      .map( Integer::parseInt )       // Methode Integer.parseInt(String)

in unserem Beispiel das Ergebnis mittels:

      .map( Integer::new )            // Konstruktor Integer(String)

Mit der Konstruktorreferenz gibt es vier Möglichkeiten, funktionale Schnittstellen zu implementieren; die drei verbleibenden Varianten sind Lambda-Ausdrücke, Methodenreferenzen und klassische Implementierung über eine Klasse.

Beispiel: Die funktionale Schnittstelle sei:

interface DateFactory { Date create(); }

Die folgende Konstruktorreferenz bindet den Konstruktor an die Methode create() der funktionalen Schnittstelle:

DateFactory factory = Date::new;
 System.out.print( factory.create() ); // zum Beispiel Sat Dec 29 09:56:35 CET 2012

Beziehungsweise die letzten beiden Zeilen zusammengefasst:

System.out.println( ((DateFactory)Date::new).create() );

Soll nur der Standard-Konstruktor aufgerufen werden, muss die funktionale Schnittstelle nur eine Methode besitzen, die keinen Parameter besitzt und etwas zurückliefert. Der Rückgabetyp der Methode muss natürlich mit dem Klassentyp zusammenpassen. Das gilt für den Typ DateFactory aus unserem Beispiel. Doch es geht noch etwas generischer, zum Beispiel mit der vorhandenen funktionalen Schnittstelle Supplier, wie wir gleich sehen werden.

In der API finden sich oftmals Parameter vom Typ Class, die als Typangabe dazu verwendet werden, dass über den Constructor der Class mit der Methode newInstance() Exemplare gebilder werden. Der Einsatz von Class lässt sich durch eine funktionale Schnittstelle ersetzen, und Konstruktorreferenzen lassen sich an Stelle von Class-Objekten übergeben.

Standard- und parametrisierte Konstruktoren

Beim Standard-Konstruktor hat die Methode nur eine Rückgabe, bei einem parametrisierten Konstruktor muss die Methode der funktionalen Schnittstelle natürlich über eine kompatible Parameterliste verfügen:

Konstruktor Date() Date(long t)
Kompatible funktionale Schnittstelle interface DateFactory {
Date create();
}
interface DateFactory {
Date create(long t);
}
Konstruktorreferenz DateFactory factory =
Date::new;
DateFactory factory =
Date::new;
Aufruf factory.create(); factory.create(1);

Tabelle 1.5: Standard- und parametrisierter Konstruktor mit korrespondierenden funktionalen Schnittstellen

Hinweis: Kommt die Typ-Inferenz des Compilers an ihre Grenzen, sind zusätzliche Typinformationen gefordert. In diesem Fall werden hinter dem Doppelpunkt in eckigen Klammen weitere Angaben gemacht, etwa Klasse::<Typ1, Typ2>new.

Nützliche vordefinierte Schnittstellen für Konstruktorreferenzen

Die für einen Standard-Konstruktor passende funktionale Schnittstelle muss eine Rückgabe besitzen und keinen Parameter annehmen; die funktionale Schnittstelle für einen parametrisierten Konstruktor muss eine entsprechende Parameterliste haben. Es kommt nun häufig vor, dass der Konstruktor ein Standard-Konstruktor ist oder genau einen Parameter annimmt. Hier ist es vorteilhaft, dass für diese beiden Fälle die Java-API zwei praktische (generisch deklarierte) funktionale Schnittstellen mitbringt:

Funktionale
Schnittstelle
Funktions-
Deskriptor
Abbildung Passt auf
Supplier<T> T get() () → T Standard-Konstruktor
Function<T,R> R apply(T t) (T) → R einfacher parametrisierter Konstruktor

Tabelle 1.6: Vorhandene funktionale Schnittstellen als Erzeuger

Beispiel: Die funktionale Schnittstelle Supplier<T> hat eine T get()-Methode, die wir mit dem Standard-Konstruktor von Date verbinden können:

Supplier<Date> factory = Date::new;
 System.out.print( factory.get() );

Wir nutzen Supplier mit dem Typparameter Date, was den parametrisierten Typ Supplier<Date> ergibt, und get() liefert folglich den Typ Date. Der Aufruf factory.get() führt zum Aufruf des Konstruktors.

Ausblick *

Besonders interessant werden die Konstruktorreferenzen mit den neuen Bibliotheksmethoden der Stream-API. Nehmen wir eine Liste vom Typ Zeitstempel an. Der Konstruktor Date(long) nimmt einen solchen Zeitstempel entgegen, und mit einem Date-Objekt können wir Vergleiche vornehmen, etwa ob ein Datum hinter einem anderen Datum liegt. Folgendes Beispiel listet alle Datumswerte auf, die nach dem 1.1.2012 liegen:

Long[] timestamps = { 2432558632L, 1455872986345L };
 Date thisYear = new GregorianCalendar( 2012, Calendar.JANUARY, 1 ).getTime();
 Arrays.stream( timestamps )
       .map( Date::new )
       .filter( thisYear::before )
       .forEach( System.out::println );  // Fri Feb 19 10:09:46 CET 2016

Die Konstruktorreferenz Date::new hilft dabei, das long mit dem Zeitstempel in ein Date-Objekt zu konvertieren.

Denksportaufgabe: Ein Konstruktor kann als Supplier oder Function gelten. Problematisch sind mal wieder geprüfte Ausnahmen. Der Leser soll überlegen, ob der Konstruktor URI(String str) throws URISyntaxException über URI::new angesprochen werden kann.

[1] Da new ein Schlüsselwort ist, kann keine Methode so heißen; der Identifizierer ist also sicher.

Was Eclipse bisher bei Java 9 unterstützt

Siehe https://wiki.eclipse.org/Java9/Examples

Feature / Steps
Expected Result

The Pre-requisite: Java 9 JRE Support

Add Java 9 JRE
Use Eclipse Preferences -> Java -> Installed JREs -> Add

Addj9.jpg

Java 9 JRE recognized as a valid JRE

Project JRE
In Package Explorer Use Project Context Menu and add Java 9 JRE
JRE specific (eg Object) gets resolved in the project.

Package Explorer
Go to Package Explorer and expand the Java 9 JRE
Modules (eg java.base etc) are listed in the package explorer view

The First Step: Module Creation

Manual
Context Menu of src -> New -> File – give the module-info.java as name
no compiler errors

Automatic
Context Menu of Project -> Cofigure -> Create module-info.

AutomoduleCreate.jpg

A default module-info.java with all packages exported should be created

Basic Necessity : Compilation, Module Dependency & Error Reporting

Unspecified Dependency
create projects "first" and "second" and create module-info.java files in each of giving the module names "first" and "second" respectively.

In the first module add the directive requires second; . This initial configuration would look something similar to the one shown in the figure.
Initmods.jpg

Compiler gives error "second cannot be resolved to a module"

Define Dependency
In the above scenario, add Project second as a dependent project for project first
Compiler error goes away

Duplicate Dependency
Continuing from the above scenario, add a duplicate requires second; directive in the module-info.java file of the first
Compiler gives error "Duplicate requires entry: second"

Circular Dependency
add a circular dependency ie

add second project dependent on first
add requires first; directive in the module-info.java file of the second project ie replace // empty by design comment by this directive

Two compiler errors " Cycle exists in module dependencies, Module second requires itself via first"

Editing with Ease: Completion in module-info.java file

Keyword Completion (1)
In the module-info.java file of say second project, after module first {, press completion key (for eg, ctrl+space in windows)

Keycomplete1.jpg

keywords exports, opens, requires, provides and uses shown

Keyword Completion (2)
after exports packagename, or opens packagename press completion key
keyword to is shown as an option

Package Completion
after exports, opens, provides or uses, press completion key
package completion shown.

Type Reference Completion
after exports, opens, provides or uses, or optionally after a dot after a package, ie exports packagename. press completion key
Type completion shown.

Implementation TypeRef Completion
after provides typename with press completion key
Type completion shown and these typereferences are implementations of the type given before with.

The Essential Utilities: Code Select, Hover, Navigate, Search and Rename

Module Select & Hover

In the module-info.java file of the first project, select second in the requires second; directive
Hover.jpg

Hover appears

Module Select, Hover & Navigate
In the above scenario, after hover appears, click on the navigate
module-info.java file of second opened

Module Select, & Search

In the module-info.java file of the second project, select second in module declaration module second { and search for references
Modsearch2.jpg

In the search view, the reference in directive requires second; in file first -> module-info.java is shown.

Package Search

create package pack1 to the project first.
add exports pack1; directive in module-info.java file of first.
search for references of pack1

In the search view, the reference of pack1 in directive exports pack1; in file first -> module-info.java is shown, similar to other pack1references if any

Type Search
create Type X in the project first, add directive uses X; in module-info.java file of first, and search for references of X
In the search view, the reference of Xin directiveuses X; in file first -> module-info.java is shown, similar to other Xreferences if any

Code Select & Rename
in module-info.java file of first, select X in directive uses X; and rename to X11
rename exhibits usual behavior – renames definition and references of Xto X11

The Outlier: Milling Project Coin Enhancements

@Safevarargs
@SafeVarargs is now allowed on private instance methods. There is even a support of quick assist for that. Use the following code which has warnings, and use the quick assist at the point mentioned in the comment

package packsafe;
import java.util.ArrayList;
import java.util.List;   public class SafeVar {
	private int getLen(List<String>...list) {
		List<String>[] l = list;
		return l.length;
	}   public static void main(String[] args) {
		SafeVar x = new SafeVar();
		List<String> l = new ArrayList<>();
		int len = x.getLen(l); // Use Quick Assist of SafeVarargs here<br>
		System.out.println("Length:" + len);
	}
}

@SafeVarargsinserted before getLen() and the warnings go away

Effectively Final Autoloseables

Effectively-final variables are allowed to be used as resources in the try-with-resources statement. The code below has an error. Try removing the line t1 = null; // Remove this code .

package packtry;
import java.io.Closeable;
import java.io.IOException;   class Two implements Closeable {
	@Override
	public void close() throws IOException {
		// nothing
	}
}
public class TryStmtTwo {   public void foo() throws IOException {
		Two t1 = new Two();
		try (t1; final Two t2 = new Two()) {
		// Empty by design
	}
	t1 = null; // Remove this code
	}
	public static void main(String[] args) {
		System.out.println("Done");
	}
}

Code without errors. For the more inquisitive, check the generated code to see that the close is generated for t1 as well which is not a final variable but an effectively final variable.

Anonymous Diamond

In the following code, there is a warning about Y being a raw type and need to be parameterized. with Java 9 support, just add a diamond operator after Y.

public class Dia {
@SuppressWarnings("unused")
	public static void main(String[] args) {
		Y<?> y1 = new Y(){}; // Change this to new Y<>(){}
	}
}
class Y<T> {}

Diamond operator <>accepted and code compiles without warning

Illegal Underscore

Underscore is an illegal identifier from Java 9 onwards. Uncomment the commented line in the following example

public class UnderScore {
	//Integer _ ;
}

error: "’_‘ should not be used as an identifier, since it is a reserved keyword from source level 1.8 on"

Private Methods

private interface methods are allowed. Change the default of worker to private

public interface I {
	default void worker() {}<   default void foo() {
		worker();
	}   default void bar() {
		worker();
	}
}

Code compiles with privateas well. Note that this is a useful feature if two default methods wants to share the worker code and does not want the worker to be an public interface method.

Reaktive Programmierung und die Flow-API

Sollen Produzenten und Konsumenten entkoppelt werden, so gibt es eine Reihe von Möglichkeiten. Nehmen wir zum Beispiel einen Iterator, der eine einfache API definiert, sodass sich auf immer die gleiche Art und Weise Daten von einem Produzenten abholen lassen. Oder nehmen wir einen Beobachter (Observer/Listener): ein Produzent verschickt seine Daten an Interessenten. Oder nehmen wir ein Bussystem: Publisher (Sender) und Subscriber (Empfänger) senden Datenpakete über einen Bus. All diese Design-Pattern sind für bestimmte Anwendungsfälle gut, haben jedoch alle eine Schwäche: es entstehen oft Blockaden und die Gefahr der Überflutung mit Nachrichten besteht.

Neu in Java 9 sind Schnittstellen eingezogen, die vorher unter http://www.reactive-streams.org/ diskutiert und als Defacto-Standard gelten. Ziel ist die Standardisierung von Programmen, die „reaktiv“ programmiert sind. Grundidee ist, dass es Publisher und Subscriber gibt, die einen asynchron Datenstrom austauschen aber nicht blockieren und mit „Gegendruck“ (engl. back pressure) arbeiten. Vergleichen wir das mit dem bekannten Bussystem, dann wird das Problem schnell klar; es kann passieren, dass ein Publisher viel zu schnell Ereignisse produziert, und die Subscriber nicht mitkommen, wo sollen dann die Daten hin? Oder, wenn der Publisher nichts sendet, dann wartet der Subscriber blockierend. Genau diese Probleme möchte das reaktive Modell vermeiden, in dem zwischen Publisher und Subscriber liegt ein Vermittler eingeschaltet wird, der über Gegendruck steuert, dass der Subscriber Daten anfordert und der Publisher dann so viel sendet, wie nicht-blockierend verarbeitet werden kann. Das realisiert eine Flusskontrolle, sodass wir auch von der Flow-API sprechen.

Die API ist untergebraucht in einer finalen Klasse java.util.concurrent.Flow, die vier statische innere Schnittstellen deklariert:

@FunctionalInterface  

public static interface Flow.Publisher<T> { 

    public void    subscribe(Flow.Subscriber<? super T> subscriber); 

}  

 

public static interface Flow.Subscriber<T> { 

    public void    onSubscribe(Flow.Subscription subscription); 

    public void    onNext(T item) ; 

    public void    onError(Throwable throwable) ; 

    public void    onComplete() ; 

}  

 

public static interface Flow.Subscription { 

    public void    request(long n); 

    public void    cancel() ; 

}  

 

public static interface Flow.Processor<T,R>  extends Flow.Subscriber<T>, Flow.Publisher<R> { 

}

Es ist gar nicht Aufgabe der Java SE diverse Implementierungen für die Schnittstellen bereitzustellen; es gibt lediglich von Publisher eine Implementierung, java.util.concurrent.SubmissionPublisher<T> . Allgemein sind die Implementierungen aber nicht trivial und daher ist es ratsam, sich mit zum Beispiel RxJava, „Reactive Extensions for the JVM“ (https://github.com/ReactiveX/RxJava) und Akka Streams (http://doc.akka.io/docs/akka/2.5.3/java/stream/index.html) zu beschäftigen, die Datenströme sehr schön in eine Kette verarbeiten können. Der eigentliche Wert der Java SE-Schnittstellen ist, dass es damit zu einer offiziellen API wird und Algorithmen problemlos unter den reaktiven Bibliotheken ausgetauscht werden können.

null-Prüfungen mit eingebauter Ausnahmebehandlung

Traditionell gilt es, null als Argument und in den Rückgaben zu vermeiden. Es ist daher gut, als Erstes in einem Methodenrumpf zu testen, ob die Argumente ungleich null sind – es sei denn, das ist unbedingt gewünscht.

Für diese Tests, dass Referenzen ungleich null sind, bietet Objects ein paar requireNonNullXXX(…)-Methoden, die null-Prüfungen übernehmen und im Fehlerfall eine NullPointerException auslösen. Diese Tests sind praktisch bei Konstruktoren oder Settern, die Werte initialisieren sollen, aber verhindern möchten, dass null durchgeleitet wird.

Beispiel: Die Methode setName(…) soll kein name-Argument gleich null erlauben:

public void setName( String name ) {
  this.name = Objects.requireNonNull( name );
 }

Alternativ ist eine Fehlermeldung möglich:

public void setName( String name ) {
  this.name = Objects.requireNonNull( name, "Name darf nicht null sein!" );
 }

class java.util.Objects

  • static<T>TrequireNonNull(Tobj)
    Löst eine NullPointerException aus, wenn obj gleich null Sonst liefert sie obj als Rückgabe. Die Deklaration ist generisch und so zu verstehen, dass der Parametertyp gleich dem Rückgabetyp ist.
  • static<T>TrequireNonNull(Tobj,Stringmessage)
    Wie requireNonNull(obj), nur dass die Meldung der NullPointerException bestimmt wird.
  • static<T>TrequireNonNull(Tobj,Supplier<String>messageSupplier)
    Wie requireNonNull(obj, message), nur kommt die Meldung aus dem messageSupplier. Das ist praktisch für Nachrichten, deren Aufbau teurer ist, denn der Supplier schiebt die Kosten für die Erstellung des Strings so lange hinaus, bis es wirklich zu einer NullPointerException kommt, denn erst dann ist die Meldung nötig.
  • static <T> T requireNonNullElse(T obj, T defaultObj)
    Liefert das erste Objekte, was nicht null defaultObj nicht null sein darf, sonst folgt eine NullPointerException. Implementiert als return (obj != null) ? obj : requireNonNull(defaultObj, „defaultObj“); Neu in Java 9.
  • static <T> T requireNonNullElseGet(T obj, Supplier<? extends T> supplier)
    Liefert das erste Objekt, was nicht null Ist obj gleich null, holt sich die Mehode die Referenz aus dem Supplier, der dann kein null liefern darf, sonst folgt eine NullPointerException. Neu in Java 9.

Statische ofXXX(…)-Methoden zum Aufbau unveränderbarer Set-, List-, Map-Datenstrukturen

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“.

 

Die Schnittstelle Checksum

Wir finden Zugang zur Prüfsummenberechnung über die Schnittstelle java.util.zip.Checksum, die für ganz allgemeine Prüfsummen steht. Eine Prüfsumme wird entweder für ein Feld oder ein Byte berechnet. Checksum liefert die Schnittstelle zum Initialisieren und Auslesen von Prüfsummen, die die konkreten Prüfsummen-Klassen implementieren müssen.

interface java.util.zip.Checksum

  • longgetValue()
    Liefert die aktuelle Prüfsumme.
  • voidreset()
    Setzt die aktuelle Prüfsumme auf einen Anfangswert.
  • voidupdate(intb)
    Aktualisiert die aktuelle Prüfsumme mit dem Byte in b.
  • voidupdate(byte[]b,intoff,intlen)
    Aktualisiert die aktuelle Prüfsumme mit den Bytes aus dem Array.
  • default public void update(byte[] b)
    Implementiert als update(b, 0, b.length); – neu in Java 9.
  • default public void update(ByteBuffer buffer)
    Aktualisiert die Prüfsumme mit den Bytes aus dem buffer. Neu in Java 9.

Die Standardbibliothek bietet bisher drei Klassen für die Prüfsummenberechnung als Implementierungen von Checksum:

  • util.zip.CRC32: CRC-32 basiert auf einer zyklischen Redundanzprüfung und testet etwa ZIP-Archive oder PNG-Grafiken. Nativ in C programmiert.
  • util.zip.CRC32C: CRC-32C nutzt ein anderes Polynom als CRC-32, verfolgt aber das gleiche Berechungsprinzip. Das JDK implementiert es in purem Java und nicht nativ. Die Ausführungszeit kann dennoch besser sein. Neu in Java 9.
  • util.zip.Adler32: Die Berechnung von CRC-32-Prüfsummen kostet viel Zeit. Eine Adler-32-Prüfsumme kann wesentlich schneller berechnet werden und bietet eine ebenso geringe Wahrscheinlichkeit, dass Fehler unentdeckt bleiben.

Die Klasse CRC32

Oft sind Polynome die Basis der Prüfsummenberechnung. Eine häufig für Dateien verwendete Prüfsumme ist CRC-32.

Nun lässt sich zu einer 32-Bit-Zahl eine Prüfsumme berechnen, die genau für diese 4 Byte steht. Damit bekommen wir aber noch keinen ganzen Block kodiert. Um das zu erreichen, berechnen wir den Wert eines Zeichens und XOR-verknüpfen den alten CRC-Wert mit dem neuen. Jetzt lassen sich beliebig Blöcke sichern. Die Berechnung ist insgesamt sehr zeitaufwändig, und Adler-32 stellt eine schnellere Alternative dar.

Beispiel

Die Klasse CRC32 berechnet eine Prüfsumme über alle durchlaufenden Bytes, die gereicht werden als einzelne Bytes oder Felder. In aller Kürze sieht ein Programm zur Berechnung von Prüfsummen für ein paar Eingaben folgendermaßen aus:

CRC32 crc = new CRC32();
crc.update( 1 );
crc.update( new byte[]{ 2, 3, 4, 5, 6, 7 } );
System.out.println( crc.getValue() ); // 1894017160

CRC32 implementiert nicht nur alle Methoden, sondern fügt noch zwei Methoden und natürlich einen Konstruktor hinzu:

Multiply-Accumulate, fma(…)

Die mathematische Operation a × b + c  kommt in Berechnungen oft vor, sodass Prozessoren heute diese Berechung optimiert – das heißt schneller und mit besserer Genauigkeit – durchführen können. In Java 9 gibt es bei Math und StrictMath die neue Methode fma(…) für die “fused multiply-accumulate“-Funktion aus dem IEEE 754 Standard.

class java.lang.Math
class java.lang.StrictMath

  • static double fma(double a, double b, double c)
  • static float fma(float a, float b, float c)
    Führt a × b + c mit dem HALF_EVEN durch. Gegenüber einem einfachen a * b + c berücksichtigt fma(…) die Besonderheiten Unendlich und NaN.

Erweiterung von @Deprecated

In Java 9 wurde der Annotationstyp @Deprecated um zwei Eigenschaften erweitert:

  • String since() default „“. Dokumentiert die Version, seit der das Element veraltet ist
  • boolean forRemoval() default false. Zeigt an, dass das Element in Zukunft gelöscht werden soll.

Beispiel

Ein Beispiel aus der Thread-Klasse:

@Deprecated(since="1.2", forRemoval=true)

public final synchronized void stop(Throwable obj) {

  throw new UnsupportedOperationException();

}

Der Paketname mit Class#getPackageName()

Seit Java 9 gibt es die Methode getPackageName(), die ausschließlich den Paketnamen liefert. Das funktioniert auch auf Class-Objekten von Arrays und primitiven Datentypen.

Beispiel: Alle folgenden Ausgaben sind java.lang.

System.out.println( System.class.getPackageName() );       // java.lang

System.out.println( Thread.State.class.getPackageName() ); // java.lang

System.out.println( byte.class.getPackageName() );         // java.lang

System.out.println( byte[].class.getPackageName() );       // java.lang

 

Beispiel: Finde heraus, ob zwei Klassen im gleichen Paket liegen:

static boolean isSameClassPackage( Class<?> c1, Class<?> c2 ) {

  return   ( c1.getClassLoader() == c2.getClassLoader() )

        && ( c1.getPackageName().equals( c2.getPackageName() ) );

}

Private Attribute auslesen/ändern und der Typ AccessibleObject

Wenn es der Sicherheitsmanager zulässt, kann ein Programm auch private- oder protected-Attribute ändern und Methoden/Konstruktoren eingeschränkter Sichtbarkeit aufrufen. Die Schlüsselfigur in diesem Spiel ist die Oberklasse java.lang.reflect.AccessibleObject, die den Klassen Field und Excecutable (und damit Constructor und Method) die Methode setAccessible(boolean) vererbt. Ist das Argument true und lässt der Sicherheitsmanager die Operation zu, lässt sich auf jedes Element (also Konstruktor, Attribut oder Methode) ungleich der Sichtbarkeitseinstellungen zugreifen:

public class ReadPrivate {
 
   @SuppressWarnings( "unused" )
   private String privateKey = "Schnuppelhase";
 
   public static void main( String[] args ) throws Exception {
     ReadPrivate key = new ReadPrivate();
     Class<?> c = key.getClass();
     java.lang.reflect.Field field = c.getDeclaredField( "privateKey" );
     field.setAccessible( true );
     System.out.println( field.get(key) ); // Schnuppelhase
     field.set( key, "Schnuckibutzihasidrachelchen");
     System.out.println( field.get(key) ); // Schnuckibutzihasidrachelchen
   }
 }

class java.lang.reflect.AccessibleObject
implements AnnotatedElement

  • void setAccessible(boolean flag)
    Nachfolgede Abfragen sollten die Java-Sichtbarkeiten ignorieren. Falls das nicht erlaubt ist, gibt es eine InaccessibleObjectException.
  • final boolean trySetAccessible()
    Wie setAccessible(true), nur löst die Methode beim nicht gewährten Zugriff keine InaccessibleObjectException aus, sondern liefert false. Neu in Java 9.

Warnung: Mit dieser Technik lässt sich viel Unsinn anrichten. Es gibt Dinge, die in der Laufzeitumgebung einfach fest sein müssen. Dazu zählen einmal angelegte Strings oder Wrapper-Objekte. Strings sind immutable, weil sie intern in einem privaten char-Feld gehalten werden und es keine Modifikationsmöglichkeiten gibt. Auch Wrapper-Objekte sind, wenn sie einmal mit einem Konstruktor angelegt wurden, nicht über öffentliche Methoden veränderbar. Sie anschließend per Reflection zu modifizieren, bringt große Unordnung, insbesondere bei den gecachten Integer/Long-Wrapper-Objekten, die die statischen valueOf(…)-Methoden liefern.

Schwache Referenzen und Cleaner

Die Referenzen auf Objekte, die wir im Alltag um uns herum haben, heißen starke Referenzen, weil die automatische Speicherbereinigung niemals ein benutztes Objekt freigeben würde. Neben den starken Referenzen gibt es jedoch auch schwache Referenzen, die es dem GC erlaubt, die Objekte zu entfernen. Was erst einmal verrückt klingt wird dann interessant, wenn es um die Implementierung von Caching-Datenstrukturen geht; ist das Objekt im Cache, ist das schön und der Zugriff schnell – ist das Objekt nicht im Cache, dann ist das auch in Ordnung, und der Zugriff dauert etwas länger. Wir können schwache Referenzen also gut verwenden, um im Cache liegende Objekte aufzubauen, die die automatische Speicherbereinigung wegräumen darf, wenn es im Speicher knapp wird.

Schwache Referenzen interagieren also in einer einfachen Weise mit der automatischen Speicherbereinigung und dafür gibt es im java.base-Modul im Paket java.lang.ref ein paar Typen. Am Wichtigsten sind die Behälter (engl. reference object gennant), die wie ein Optional ein Objekt referenzieren, das aber plötzlich verschwunden sein kann:

  • SoftReference<T>. Ein Behälter für softly reachable Objekte. Die Objekte werden vom GC spät freigegeben, wenn es kurz vor einem OutOutMemoryError
  • WeakReference<T>. Ein Behälter für weakly reachable Objekte. Die Objekte werden vom GC schon relativ früh beim ersten GC freigegeben.
  • PhantomReference<T>. Ein Behälter, der immer leer ist, aber dazu dient mitzubekommen, wenn der GC sich von einem Objekt trennt.
  • Reference<T>. Abstakte Basisklasse von PhantomReference, SoftReference, WeakReference.

Die Behälter selbst werden vom GC nicht entfernt, sodass eine ReferenceQueue<T> ein Abfragen erlaubt, um festzustellen, welche Reference-Behälter leer sind und z. B. aus einer Datenstruktur entfernt werden können – leere Behälter sind nutzlos und können nicht wieder recycelt werden.

Ein neuer Typ ab Java 9 im Paket ist Cleaner, der eine Alternative zur Finalizierung ist. Beim Cleaner lässt sich eine Operation (vom Typ Cleaner.Cleanable) anmelden, die immer dann aufgerufen wird, wenn die automatische Speicherbereinigung zuschlägt und das Objekt nicht mehr erreichbar ist. Intern greift die Klasse auf PhantomReference zurück.

Beispiel: Lege einen Punkt an, registriere einen Cleaner und rege danach den GC an. Eine Konsolenausgabe „Punkt ist weg!“ ist wahrscheinlich:

Point p = new Point( 1, 2 );

Cleaner.create().register( p, () -> System.out.println( "Punkt ist weg!" ) );

p = null;

byte[] bytes = new byte[ (int) Runtime.getRuntime().freeMemory() ];

Auf keinen Fall darf die Aufräumoperation p wieder referenzieren.

 

Boolean nach Ganzzahl konvertieren

Der primitive Typ boolean lässt sich nicht über eine Typumwandlung in einen anderen primitiven Typ konvertieren. Doch in der Praxis kommt es vor, dass true auf 1 und false auf 0 abgebildet werden muss; der übliche Weg ist:

int val = aBoolean ? 1 : 0;

Exotischer ist:

int val = Boolean.compare( aBoolean, false );

Noch exotischer folgendes:

int val = 1 & Boolean.hashCode( true ) >> 1;

Nachträgliches Implementieren von Schnittstellen

Implementiert eine Klasse eine bestimmte Schnittstelle nicht, so kann sich auch nicht am dynamischen Binden über diese Schnittstelle teilnehmen, auch wenn sie eine Methoden hat, über die eine Schnittstelle abstrahiert. Besitzt zum Beispiel die nicht-finale Klasse FIFA eine öffentliche Methode price(), implementiert aber Buyable mit einer gleich benannten Methoden nicht, so lässt sich zu einem Trick greifen, sodass eine Implementierung geschaffen wird, die die existierende Methode aus der Klasse und die der Schnittstelle in die Typhierarchie bringt.

class FIFA {
  public double price() { … }
}

interface Buyable {
   double price();
 }

class FIFAisBuyable extends FIFA implements Buyable { }

Eine neue Unterklasse FIFAisBuyable erbt von der Klasse FIFA und implementiert die Schnittstelle Buyable, sodass der Compiler die existierende price()-Methode mit Vorgabe der Schnittstelle vereinigt. Nun lässt sich FIFAisBuyable als Buyable nutzen und dahinter steckt die Implementierung von FIFA. Als Unterklasse bleiben auch alle sichtbaren Eigenschaften der Oberklasse erhalten.

Retrieve Windows netstat data and observe new network connections

import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

public class Netstat {

  public static class Protocol {

    public final String protocol;
    public final String localAddress;
    public final String remoteAddress;
    public final String status;

    public Protocol( String protocol, String localAddress, String remoteAddress, String status ) {
      this.protocol = protocol;
      this.localAddress = localAddress;
      this.remoteAddress = remoteAddress;
      this.status = status;
    }

    @Override
    public int hashCode() {
      return Objects.hash( localAddress, protocol, remoteAddress, status );
    }

    @Override
    public boolean equals( Object obj ) {
      if ( this == obj )
        return true;
      if ( obj == null )
        return false;
      if ( getClass() != obj.getClass() )
        return false;
      Protocol other = (Protocol) obj;
      if ( localAddress == null && other.localAddress != null )
        return false;
      else if ( !localAddress.equals( other.localAddress ) )
        return false;
      if ( protocol == null && other.protocol != null )
        return false;
      else if ( !protocol.equals( other.protocol ) )
        return false;
      if ( remoteAddress == null && other.remoteAddress != null )
        return false;
      else if ( !remoteAddress.equals( other.remoteAddress ) )
        return false;
      if ( status == null && other.status != null )
        return false;
      else if ( !status.equals( other.status ) )
        return false;
      return true;
    }

    @Override
    public String toString() {
      return String.format( "%-6s %-22s %-22s %s", protocol, localAddress, remoteAddress, status );
    }
  }

  private final static Pattern pattern = Pattern.compile( "(TCP|UDP)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)" );

  public static Collection<Protocol> netStat() throws IOException {
    Collection<Protocol> result = new ArrayList<>();
    Process p = new ProcessBuilder( "netstat", "-n" ).start();
    try ( Scanner scanner = new Scanner( p.getInputStream() ) ) {
      while ( scanner.findWithinHorizon( pattern, 0 ) != null )
        result.add( new Protocol( scanner.match().group( 1 ), scanner.match().group( 2 ),
                                   scanner.match().group( 3 ), scanner.match().group( 4 ) ) );
    }
    return result;
  }

  public static void main( String[] args ) throws IOException, InterruptedException {
    Set<Protocol> oldStat = new HashSet<>( netStat() );

    while ( true ) {
      TimeUnit.SECONDS.sleep( 10 );

      HashSet<Protocol> newStat = new HashSet<>( netStat() );

      Set<Protocol> differenceSet = new HashSet<>( newStat );
      differenceSet.removeAll( oldStat );

      for ( Protocol p : differenceSet )
        System.out.println( p );

      oldStat = newStat;
    }
  }
}