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.

JAX-RS mit Jersey, Teil 2 (RESTful Web-Services)

Content-Handler, Marshaller und verschiedene MIME-Typen

JAX-RS erlaubt grundsätzlich alle MIME-Typen, und die Daten selbst können auf verschiedene Java-Datentypen übertragen werden. So ist es egal, ob bei Textdokumenten zum Beispiel der Rückgabetyp String oder OutputStream ist; selbst ein File-Objekt lässt sich zurückgeben. Für einen Parametertyp – Parameter werden gleich vorgestellt – gilt das Gleiche: JAX-RS ist hier recht flexibel und kann über einen InputStream oder Writer einen String entgegennehmen. (Reicht das nicht, können so genannte Provider angemeldet werden.)

Bei XML-Dokumenten kommt hinzu, dass JAX-RS wunderbar mit JAXB zusammenspielt.

XML mit JAXB

Dazu ein Beispiel für einen Dienst hinter der URL http://localhost:8080/api/dating/serverinfo, der eine Serverinformation im XML-Format liefert. Das XML wird automatisch von JAXB generiert.

@GET
 @Path( "serverinfo" )
 @Produces( MediaType.TEXT_XML )
 public ServerInfo serverinfo() {
   ServerInfo info = new ServerInfo();
   info.server = System.getProperty( "os.name" )+" "+System.getProperty( "os.version" );
   return info;
 }

@XmlRootElement
 class ServerInfo {
   public String server;
 }

Die Klasse ServerInfo ist eine JAXB-annotierte Klasse. In der eigenen JAX-RS-Methode serverinfo() wird dieses ServerInfo-Objekt aufgebaut, das Attribut gesetzt und dann zurückgegeben; der Rückgabetyp ist also nicht String wie vorher, sondern explizit ServerInfo. Dass der MIME-Typ XML ist, sagt @Produces(MediaType.TEXT_XML). Und noch eine Annotation nutzt das Beispiel: @Path. Lokal an der Methode bedeutet es, dass der angegebene Pfad zusätzlich zur Pfadangabe an der Klasse gilt. Also ergibt sich der komplette Pfad aus: Basispfad + „dating“ + „/“ + „serverinfo“. Wir können http://localhost:8080/api/dating/serverinfo im Browser eingeben.

JSON-Serialisierung *

Ist der Client eines REST-Aufrufs ein JavaScript-Programm in einem Webbrowser, ist es in der Regel praktischer, statt XML das Datenformat JSON zu verwenden. JAX-RS bindet drei Möglichkeiten zum Senden von JSON:

  1. JSON-Übersetzungdes Java-Objekts über die JSON-Bibliothek („POJO based JSON binding support“). Es lassen sich so ziemlich alle Java-Objekte abbilden.
  2. JAXB-basierte JSON-Übersetz Die JSON-Bibliothek liest die JAXB-annotierten Objekte aus und führt anhand der Metadaten die Umsetzung durch. JAXB wird hier nicht nur für XML verwendet, sondern auch für JSON; natürlich ergeben nicht alle Eigenschaften einen Sinn.
  3. Automatisch ohne ein Mapping arbeitet eine Low-Level-API. Sie gibt maximale Flexibilität, aber erfordert viel Handarbeit. Seit Java EE 7 ist die API im Standard, aber nicht Teil der Java SE.

Schauen wir uns die zweite Lösung an. Jersey unterstützt von Haus aus Jackson, MOXy und Jettison als JSON-Objekt-Mapper. Um MOXy einzusetzen müssen weitere Java-Archive in den Klassenpfad aufgenommen werden. Wir können die Abhängigkeiten über Maven beschreiben, dann ist folgendes in der pom.xml aufzunehmen:

<dependency>

<groupId>org.glassfish.jersey.media</groupId>

<artifactId>jersey-media-moxy</artifactId>

<version>2.25</version>

</dependency>

Oder zu Fuß müssen die folgenden JAR-Dateien geladen und dann im Klassenpfad aufgenommen werden:

  • https://mvnrepository.com/artifact/org.glassfish.jersey.media/jersey-media-moxy/
  • https://mvnrepository.com/artifact/org.glassfish.jersey.ext/jersey-entity-filtering/
  • https://mvnrepository.com/artifact/org.eclipse.persistence/org.eclipse.persistence.moxy/
  • https://mvnrepository.com/artifact/org.eclipse.persistence/org.eclipse.persistence.core/
  • https://mvnrepository.com/artifact/org.glassfish/javax.json/
    • Um von XML auf JSON zu wechseln muss bei den REST-Methoden lediglich der APPLICATION_JSON eingesetzt werden:
@GET

@Path( "jsonserverinfo" )

@Produces( MediaType.APPLICATION_JSON )

public ServerInfo jsonserverinfo() {

  return serverinfo();

}

Das reicht schon aus, und der Server sendet ein JSON-serialisiertes Objekt.

Alles Verhandlungssache

Die JAX-RS-API bietet mit dem MIME-Typ noch eine Besonderheit, dass der Server unterschiedliche Formate liefern kann, je nachdem, was der Client verarbeiten möchte oder kann. Der Server macht das mit @Produces klar, denn dort kann eine Liste von MIME-Typen stehen. Soll der Server XML und JSON generieren können, schreiben wir:

@GET

@Path( "jsonxmlserverinfo" )

@Produces( { MediaType.TEXT_XML, MediaType.APPLICATION_JSON } )

public ServerInfo jsonxmlserverinfo() {

  return serverinfo();

}

Kommt der Client mit dem Wunsch nach XML, bekommt er XML, möchte er JSON, bekommt er JSON. Die Jersey-Client-API teilt über request(String) bzw. request(MediaType… types) mit, was ihr Wunschtyp ist. (Dieser Typwunsch ist eine Eigenschaft von HTTP und nennt sich Content Negotiation.)

WebTarget wr1 = ClientBuilder.newClient().target( "http://localhost:8080/api" );

Builder b1 = wr1.path( "dating" ).path( "jsonxmlserverinfo" )

                .request( MediaType.APPLICATION_JSON );

System.out.println( b1.get( ServerInfo.class ).getServer() );  // Windows 10 10.0




WebTarget wr2 = ClientBuilder.newClient().target( "http://localhost:8080/api" );

Builder b2 = wr2.path( "dating" ).path( "jsonxmlserverinfo" )

                .request( MediaType.TEXT_XML );

System.out.println( b2.get( ServerInfo.class ).getServer() );  // Windows 10 10.0




WebTarget wr3 = ClientBuilder.newClient().target( "http://localhost:8080/api" );

Builder b3 = wr3.path( "dating" ).path( "jsonxmlserverinfo" )

                .request( MediaType.TEXT_PLAIN );

try {

  System.out.println( b3.get( ServerInfo.class ).getServer() );

}

catch ( Exception e ) {

  System.out.println( e );

  // javax.ws.rs.NotAcceptableException: HTTP 406 Not Acceptable

}

Passt die Anfrage auf den Typ von @Produces, ist alles prima, ohne Übereinstimmung gibt es einen Fehler. Bei der letzten Zeile gibt es eine Ausnahme („javax.ws.rs.NotAcceptableException: HTTP 406 Not Acceptable“), da JSON und XML eben nicht purer Text sind.

REST-Parameter

Im Abschnitt „Wie sieht ein REST-URI aus?“ in Abschnitt 15.2.1 wurde ein Beispiel vorgestellt, wie Pfadangaben aussehen, wenn sie einen RESTful Service bilden:

http://www.tutego.de/blog/javainsel/category/java-7/page/2/

Als Schlüssel-Wert-Paare lassen sich festhalten: category=java-7 und page=2. Der Server wird die URL auseinanderpflücken und genau die Blog-Einträge liefern, die zur Kategorie „java-7“ gehören und sich auf der zweiten Seite befinden.

Bisher sah unser REST-Service auf dem Endpunkt /api/dating/ so aus, dass einfach ein String zurückgegeben wird. Üblicherweise gibt es aber unterschiedliche URLs, die Operationen wie „finde alle“ oder „finde alle mit der Einschränkung X“ abbilden. Bei unseren Dating-Dienst wollen wir dem Client drei Varianten zur Abfrage anbieten (mit Beispiel):

  • /api/dating/: alle Nutzer
  • /api/dating/gender/nasi: alle Nutzer mit dem Geschlecht „nasi“
  • /api/dating/gender/nasi/age/18-28: alle „nasischen“-Nutzer im Alter von 18 bis 28

Das erste Beispiel macht deutlich, dass hier ohne explizite Angabe weiterer Einschränkungskriterien alle Nachrichten erfragt werden sollen, während mit zunehmend längerer URL weitere Einschränkungen dazukommen.

Parameter in JAX-RS kennzeichnen

Die JAX-RS-API erlaubt es, dass Parameter (wie eine ID oder ein Such-String) leicht eingefangen werden können. Für die drei möglichen URLs entstehen zum Beispiel drei überladene Methoden:

@GET @Produces( MediaType.TEXT_PLAIN )
 public String meet() ...
 


@GET

@Path( "gender/{gender}" )

@Produces( MediaType.TEXT_PLAIN )

public String meet( @PathParam( "gender" ) String gender ) {

  return String.format( "Geschlecht = %s", gender );

}




@GET

@Produces( MediaType.TEXT_PLAIN )

@Path( "gender/{gender}/age/{age}" )

public String meet( @PathParam( "gender" ) String gender,

                    @PathParam( "age" ) String age ) {

  return String.format( "Geschlecht = %s, Altersbereich = %s", gender, age );

}

Die bekannte @Path-Annotation enthält nicht einfach nur einen statischen Pfad, sondern beliebig viele Platzhalter in geschweiften Klammern. Der Name des Platzhalters taucht in der Methode wieder auf, nämlich dann, wenn er mit @PathParam an einen Parameter gebunden wird. Jersey parst für uns die URL und füllt die Parametervariablen passend auf bzw. ruft die richtige Methode auf. Da die JAX-RS-Implementierung den Wert füllt, nennt sich das auch JAX-RS-Injizierung.

URL-Endung Aufgerufene Methode
/api/dating/ meet()
/api/dating/gender/nasi meet( String gender )
/api/dating/gender/nasi/age/18-28 meet( String gender, String age )

Welche URL zu welcher Methode führt

Die Implementierungen der Methoden würden jetzt an einen Daten-Service gehen und die selektierten Datensätze zurückgeben. Das zeigt das Beispiel nicht, da dies eine andere Baustelle ist.

Tipp: Wenn Parameter falsch sind, kann eine Methode eine besondere Ausnahme vom Typ javax.ws.rs.WebApplicationException (dies ist eine RuntimeException) erzeugen. Im Konstruktor von javax.ws.rs.WebApplicationException lässt sich ein Statuscode als int oder als Aufzählung vom Typ Response.Status übergeben, etwa new WebApplicationException(Response.Status. EXPECTATION_FAILED).

REST-Services mit Parametern über die Jersey-Client-API aufrufen

Wenn die URLs in dem Format schlüssel1/wert1/schlüssel2/wert2 aufgebaut sind, dann ist ein Aufbau einfach durch Kaskadierung der path(…)-Methoden umzusetzen:

System.out.println( ClientBuilder.newClient().target( "http://localhost:8080/api" )

  .path( "dating" ).path( "gender" ).path( "nasi" )

  .request().get( String.class ) ); // Geschlecht = nasi




System.out.println( ClientBuilder.newClient().target( "http://localhost:8080/api/dating" )

  .path( "gender" ).path( "nasi" ).path( "age" ).path( "18-28" )

  .request().get( String.class ) ); // Geschlecht = nasi, Altersbereich = 18-28

Multiwerte

Schlüssel-Wert-Paare lassen sich auch auf anderen Wegen übermitteln statt nur auf dem Weg über schlüssel1/wert1/schlüssel2/wert2. Besonders im Web und für Formularparameter ist die Kodierung über schlüssel1=wert1&schlüssel2=wert2 üblich. Auch das kann in JAX-RS und mit der Jersey-Client-API abgebildet werden:

  • Anstatt Parameter mit @PathParam zu annotieren, kommt bei Multiwerten @QueryParam zum Einsatz.
  • Anstatt mit path(String) zu arbeiten, wird bei dem Jersey-Client mit queryParam(String key, Object… value)

Hinweis

Es gibt eine Reihe von Dingen, die in Methoden per Annotation übermittelt werden können, und nicht nur @PathParam und @QueryParam. Dazu kommen noch Dinge wie @HeaderParam für den HTTP-Request-Header, @CookieParam für Cookies, @Context für Informationsobjekte und weitere Objekte.

PUT-Anforderungen und das Senden von Daten

Zum Senden von Daten an einen REST-Service ist die HTTP-PUT-Methode gedacht. Die Implementierung einer Java-Methode kann so aussehen:

@PUT

@Path( "message/{user}" )

@Consumes( MediaType.TEXT_PLAIN )

public Response postMessage( @PathParam( "user" ) String user, String message ) {

  System.out.printf( "%s sendet '%s'%n", user, message );

  return Response.noContent().build();

}

Zunächst gilt, dass statt @GET ein @PUT die Methode annotiert. @Consumes hält den MIME-Typ dieser gesendeten Daten fest. Ein zusätzlicher @PathParam fängt die Benutzerkennung ein, die dann mit der gesendeten PUT-Nachricht auf der Konsole ausgegeben wird.

Diese beiden Annotationen @PUT und @Consumes sind also nötig. Eine Rückgabe in dem Sinne hat die Methode nicht, und es ist umstritten, ob ein REST-PUT überhaupt neben dem Statuscode etwas zurückgeben soll. Daher ist die Rückgabe ein spezielles JAX-RS-Objekt vom Typ Response, das hier für 204, „No Content“, steht. Ein 204 kommt auch immer dann automatisch zurück, wenn eine Methode void als Rückgabe deklariert.

PUT/POST/DELETE-Sendungen mit der Jersey-Client-API absetzen

Ein Invocation.Builder bietet neben get(…) auch die anderen Java-Methoden für HTTP-Methoden, also delete(…), post(…), options(…), … und eben auch put(…) zum Schreiben.

Client client = ClientBuilder.newClient();

WebTarget target = client.target( "http://localhost:8080/api" );

Response response = target.path( "dating" ).path( "message" )

                          .path( "Ha" ).request().put( Entity.text("Hey Ha!") );

System.out.println( response );

Die put(…)-Methode erwartet als Argument den Typ Entity, und zum Objektaufbau gibt es diverse statische Methoden in Entity. Es ist Entity.text(„Hey Chris“) eine Abkürzung für Entity.entity(„Hey Chris“, MediaType.TEXT_PLAIN).

Versionierung einer REST-API

Die REST-Schnittstelle ist ein externer Endpunkt einer Software und unterliegt den gleichen Modifikationen wie übliche Software: Schnittstellen ändern sich, die ausgetauschten Objekte bekommen zusätzliche Felder, usw. Ändert sich die Schnittstelle, erzeugt das eine neue Version. Alte APIs sollte ein Server eine Zeit lang beibehalten, damit nicht von einem Tag auf dem anderen eine vielleicht große Anzahl von Klienten mit der älteren Schnittstelle kommunikationslos sind.

Prinzipiell gibt es drei Ansätze bei der Versionierung von REST-APIs; die ersten beiden Varianten nutzen die Möglichkeiten der URL bzw. des HTTP:

  1. Änderung der URL, dass etwa eine Versionskennung mit eingebaut wird. Zu finden ist das bei vielen großen Unternehmen, und zu erkennen etwa an der Versionsnummer bei https://api.twitter.com/1/ https://www.googleapis.com/youtube/v3/, https://api.pinterest.com/v1/, https://api.instagram.com/v1/.
  2. In einem HTTP-Header, wie Accepts. Dort lässt sich die Version in den Dateityp hineinkodieren. Normalerweise ist der Header mit einem MIME-Typ wie text/plain, text/html belegt, doch der RFC4288 sieht in Sektion 3.2 einen „Vendor Tree“ mit dem Präfix vor, sodass sich damit ein eigener Media-Typ inklusive Version formulieren lässt. Das kann so aussehen: application/vnd.tutego.app.api+json;version=2.1 oder application/vnd.tutego.app.api-v2.1+json.
  3. In den Rumpf einer Nachricht lässt sich ebenfalls eine Version einkodieren, zum Beispiel wenn JSON übermittelt wird: {„version“: „2.1“,…}. Nachteilig ist, dass URLs ohne Parameter und ohne Körper keinen Platz für eine Versionsnummer lassen.

Alle Lösungen lassen sich prinzipiell mit JAX-RS umsetzen, wobei die Lösung 1 am Einfachsten ist.

JAX-RS mit Jersey, Teil 1 (RESTful Web-Services)

Dieser Abschnitt stellt das Architekturprinzip REST vor und anschließend den Java-Standard JAX-RS. Es folgen Beispiele mit der JAX-RS-Referenzimplementierung Jersey.

Aus Prinzip REST

Bei RESTful Services liegt das Konzept zugrunde, dass eine Ressource über einen Webserver verfügbar ist und eindeutig über einen URI identifiziert wird.

Da unterschieden werden muss, ob eine Ressource neu angelegt, gelesen, aktualisiert, gelöscht oder aufgelistet werden soll, werden dafür die bekannten HTTP-Methoden verwendet:

HTTP-Methode Operation
GET Listet Ressourcen auf oder holt eine konkrete Ressource.
PUT Aktualisiert eine Ressource.
DELETE Löscht eine Ressource oder eine Sammlung von Ressourcen.
POST Semantik kann variieren, in der Regel aber geht es um das Anlegen einer neuen Ressource.

HTTP-Methoden und übliche assoziierte Operationen

Anders als SOAP ist REST kein entfernter Methodenaufruf, sondern eher vergleichbar mit den Kommandos INSERT, UPDATE, DELETE und SELECT in SQL.

Ursprung von REST

Die Idee, Web-Services mit dem Webstandard HTTP aufzubauen, beschrieb Roy Thomas Fielding im Jahr 2000 in seiner Dissertation „Architectural Styles and the Design of Network-based Software Architectures“. Das Architekturprinzip nennt er Representational State Transfer (REST) – der Begriff ist neu, aber das Konzept ist alt. Aber so wie im richtigen Leben setzen sich manche Dinge erst spät durch. Das liegt auch daran, dass SOAP-basierte Web-Services immer komplizierter wurden und sich die Welt nach etwas anderem sehnte. (Daher beginnen wir im Buch auch mit REST, und dann wird SOAP folgen.)

Wie sieht ein REST-URI aus?

Auf der einen Seite stehen die HTTP-Methoden GET, PUT, POST, DELETE und auf der anderen Seite die URIs, die die Ressource kennzeichnen. Ein gutes Beispiel einer REST-URL ist der Blog vom Java-Buch:

http://www.tutego.de/blog/javainsel/category/java-9/page/2/

Das Ergebnis ist ein HTML-Dokument mit ausschließlich den Beiträgen aus der Kategorie Java 9 und nur denen auf der zweiten Seite.

Fast daneben ist auch vorbei

Da auf den ersten Blick jede HTTP-Anfrage an einen Webserver wie ein REST-Aufruf aussieht, sollten wir uns im Klaren darüber sein, was denn kein REST-Aufruf ist. Eine URL wie http://www.bing.com/search?q=tutego ist erst einmal nicht der typische REST-Stil, da es keine Ressource gibt, die angesprochen wird. Da REST als leichtgewichtig und cool gilt, geben sich natürlich gerne Dienste ein bisschen RESTig. Ein Beispiel ist Flickr, ein Fotoservice von Yahoo. Das Unternehmen wirbt mit einer REST-API, aber es ist alles andere als REST und kein gutes Beispiel.[1] Das Gleiche gilt auch für Twitter, das lediglich einen rudimentären REST-Ansatz hat, aber in der Öffentlichkeit als REST pur wahrgenommen wird.

Jersey

Für Java gibt es mit JAX-RS einen Standard zum Deklarieren von REST-basierten Web-Services. Er wurde in der JSR-311, „JAX-RS: The Java API for RESTful Web Services“, definiert. Es fehlt noch eine Implementierung, damit wir Beispiele ausprobieren können:

  • Jeder Applikationsserver ab Java EE 6 enthält standardmäßig eine JAX-RS-Implementierung.
  • Da wir JAX-RS ohne einen Applikationsserver mit der normalen Java SE ausprobieren möchten, ist eine JAX-RS-Implementierung nötig, da das JDK die JAX-RS-API nicht mitbringt. Es ist naheliegend, die JAX-RS-Referenzimplementierung Jersey (https://jersey.github.io/) zu nutzen, die auch intern von Applikationsservern verwendet wird. Jersey x implementiert die JAX-RS 2.0 API.

Mit Jersey lässt sich entweder ein Servlet-Endpunkt definieren, sodass der RESTful Web-Service in einem Servlet-Container wie Tomcat läuft, oder der eingebaute Mini-HTTP-Server genutzt wird. Wir werden im Folgenden mit dem eingebauten Server arbeiten.

Download und JAR-Dateien

  • Beginnen wir mit dem Download der nötigen Java-Archive. Diese können wir einfach per Maven einbinden, oder per Hand laden. In Maven muss für unser Beispiel folgendes in die xml-Datei:
    • <dependency>
    • <groupId>org.glassfish.jersey.containers</groupId>
    • <artifactId>jersey-container-jdk-http</artifactId>
    • <version>2.25</version>
    • </dependency>

Wenn wir den händischen Weg nehmen ist es etwas aufwändiger, die einzelnen Jar-Dateien zusammenzusammeln. Die Jersey-Homepage https://jersey.github.io/ verweist für den Download auf https://jersey.github.io/download.html. Klicken wir dort auf

Jersey JAX-RS 2.0 RI bundle […] contains the JAX-RS 2.0 API jar, all the core Jersey module jars as well as all the required 3rd-party dependencies.

dann startet der Download von jaxrs-ri-2.x.y.zip. Das ZIP-Archiv können wir auspacken und dann folgende Archive in den Klassenpfad aufnehmen:

  • Aus dem Ordner api: ws.rs-api-2.0.1.jar. Enthält die JAX-RS-API (im Wesentlichen die Annotationen), aber keine Implementierung.
  • Aus dem Ordner lib: jersey-client.jar, jersey-common.jar, jersey-server.jar. Das ist die Jersey-Implementierung. jersey-media-jaxb.jar kann mit eingebunden werden, damit Jersey automatisch JAXB für die Konvertierung zwischen XML und Objekten verwendet.
  • Aus dem Ordner ext: Alle jar-Dateien. Enthält das, worauf die Jersey-Implementierung selbst zurückgreift; um es einfach zu machen: einfach alles.
  • Ein JAR müssen wir noch in den Klassenpfad dazusetzen, damit der Jersey-Server den im JDK eingebauten HTTP-Server nutzen kann, und zwar von http://central.maven.org/maven2/org/glassfish/jersey/containers/jersey-container-jdk-http/ das jersey-container-jdk-http-2.x.y.jar.

JAX-RS-Annotationen für den ersten REST-Service

JAX-RS definiert einige Annotationen, die zentrale Konfigurationen bei RESTful Web-Services vornehmen, etwa Pfadangaben, Ausgabeformate oder Parameter. In den nächsten Abschnitten werden diese Annotationen an unterschiedlichen Beispielen vorgestellt.

Die Annotationen @Path und @GET

Beginnen wir mit einem REST-Service, der einen simplen Text-String liefert:

package com.tutego.insel.rest;
 
 import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;


 @Path( "dating" )
 public class DatingResource {
 
   @GET
   @Produces( MediaType.TEXT_PLAIN )
   public String meet() {
     return "Afra, Ange, Ceri, Dara, Ha, Jun, Sevan";

  }
 }

Die ersten beiden Annotationen sind zentral:

  • @Path: Ist die Pfadangabe, die auf den Basispfad gesetzt wird. Die Annotation ist gültig für einen Typ und eine Methode.
  • @GET: Die HTTP-Methode. Hier gibt es auch die Annotationen für die anderen HTTP-Methoden POST, PUT, …
  • @Produces: Die Annotation kann zwar grundsätzlich auch entfallen, aber besser ist es, deutlich einen MIME-Typ zu setzen. Es gibt String-Konstanten für die wichtigsten MIME-Typen, wie TEXT_XML oder MediaType.TEXT_HTML, und auch Strings wie „application/pdf“ können direkt gesetzt werden. Neben @Produces gibt es das symmetrische @Consumes.

Test-Server starten

Ein Java EE-Application-Server würde die Klasse aufgrund der Annotationen gleich einlesen und als REST-Service anmelden. Da wir die Klasse jedoch in der Java SE ohne Application-Server verwenden, muss ein REST-Server von Hand aufgebaut werden. Das ist aber problemlos, denn dafür haben wir jersey-container-jdk-http-2.x.y.jar eingebunden, sodass sich Jersey in den im JDK eingebauten HTTP-Server integriert.

ResourceConfig rc = new ResourceConfig().packages( "com.tutego.insel.rest" );
 HttpServer server = JdkHttpServerFactory.createHttpServer( 
    URI.create( "http://localhost:8080/api" ), rc );
 JOptionPane.showMessageDialog( null, "Ende" );
 server.stop( 0 );

Nach dem Start des Servers scannt Jersey die Klassen im genannten Paket daraufhin ab, ob sie passende JAX-RS-Annotationen tragen. Das Class-Scanning wurde aktiviert mit der package(…)-Methode beim ResourceConfig, es ist aber auch möglich, im Konstruktor von ResourceConfig direkt die JAW-RS-Ressourcen über ihre Class-Objekte aufzuzählen.

Die statische Methode JdkHttpServerFactory.createHttpServer(…) liefert ein Objekt vom Typ com.sun.net.httpserver.HttpServer, das Teil der internen Java-API ist. Die JdkHttpServerFactory stammt aus dem internen Jersey-Paket.

REST-Services konsumieren

Bei createHttpServer(…) ist als Testpfad „http://localhost:8080/api“ eingetragen, und zusammen mit @Path(„dating“) an der Klasse ergibt sich der Pfad http://localhost:8080/api/dating für die Ressource. Wir können die URL im Browser eintragen.

REST-Client mit Jersey

Sich im Browser das Ergebnis eines REST-Aufrufs anzuschauen, ist nicht das übliche Szenario. Oft sind es JavaScript-Programme im Browser, die REST-Aufrufe starten und die konsumierten Daten auf einer Webseite integrieren.

Ist es ein Java-Programm, das einen REST-Dienst anzapft, so kann dies mit einer einfachen URLConnection erledigt werden, was eine Standardklasse aus dem java.net-Paket ist, um auf HTTP-Ressourcen zuzugreifen. Doch Jersey definiert eine standardisierte API zum einfachen Zugriff. Es reicht ein Einzeiler:

System.out.println(
   ClientBuilder.newClient().target( "http://localhost:8080/api" )
                .path( "dating" ).request().get( String.class )
 );

Der API-Stil, den die Bibliotheksautoren hier verwenden, nennt sich Fluent-API; Methodenaufrufe werden verkettet, um alle Parameter wie URL oder MIME-Typ für die Anfrage zu setzen. Das ist eine Alternative zu den bekannten Settern auf JavaBeans bzw. einen überladenen Konstruktor mit unübersichtlichen Parameterlisten.

Um die Typen bei der javax.ws.rs.client-API etwas besser zu verstehen, schreiben wir alles ganz vollständig aus:

Client client = ClientBuilder.newClient();
 WebTarget target = client.target( "http://localhost:8080/api" );
 WebTarget resourceTarget = target.path( "dating" );
 Invocation.Builder request = resourceTarget.request( MediaType.TEXT_PLAIN );
 Response response = request.get();
 System.out.println( response.readEntity( String.class ) );

Das Response-Objekt hat auch eine Methode getStatus() für den HTTP-Statuscode und hasEntry(), um zu prüfen, ob es ein Ergebnis gibt und keinen Fehler. Apropos Fehler: Ist die URL falsch, gibt es eine RuntimeException – geprüfte Ausnahmen verwendet Jersey nicht.

Exception in thread „main“ javax.ws.rs.NotFoundException: HTTP 404 Not Found

[1]   Roy Fielding meint dazu nur: „Flickr obviously don’t have a clue what REST means since they just use it as an alias for HTTP. Perhaps that is because the Wikipedia entry is also confused. I don’t know.“ (Quelle: http://roy.gbiv.com/untangled/2008/no-rest-in-cmis).

Eclipse Oxygen (4.7) ist fertig

https://projects.eclipse.org/releases/oxygen

Alle Neuerungen unter https://www.eclipse.org/oxygen/noteworthy/.

 

Unter Java 9 bedarf es Vorbereitungen für den Start:

Launching on Java 9

If you are launching eclipse with a Java 9 build (either via system or via -vm option in eclipse.ini file) then, in eclipse.ini file, after -vmargs line add --add-modules=ALL-SYSTEM. This should launch eclipse. If you get an IllegalAccessException or InaccessibleObjectException, you can add --permit-illegal-access additionally after the -vmargs.

e.g.

...
--launcher.appendVmargs
-vmargs
--add-modules=ALL-SYSTEM
--permit-illegal-access
...

The switches can be entered on the command line after -vmargs, e.g.

$ ./eclipse -vm *[path to jdk9]*/bin -vmargs --add-modules=ALL-SYSTEM --permit-illegal-access

Weiterhin:

Support for Building Java 9 Applications

The Java™ 9 specification has not been released yet and so support has not yet been integrated into our standard download packages. You can add an early access preview to the Eclipse IDE, Oxygen Edition (4.7).

The Eclipse Java™ 9 Support (BETA) contains the following:

  • ability to add JRE and JDK 9 as installed JRE;
  • support for JavaSE-9 execution environment;
  • ability to create Java and Plug-in projects that use a JRE or JDK 9; and
  • ability to compile modules that are part of a Java project
  • For up-to-date information, please see Java 9 Support (BETA) for Oxygen in the Eclipse Marketplace.

Install the beta by dragging the install button onto your running Eclipse IDE, Oxygen Edition instance.

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.

JSON-Verarbeitung mit der Java API for JSON Processing

Zum Verarbeiten von JSON-Dokumenten gibt es in der Java SE keine Standardklassen, sodass sich eine Reihe von Open-Source-Bibliotheken herausgeprägt haben; Jackson (http://tutego.de/go/jackson) gehört zu den populärsten Lösungen. 2013 wurde dann die JSR 353, „Java API for JSON Processing“ verabschiedet, die Teil der Java EE 7 ist.

Wir können die JSON-API in unseren Java SE-Programmen nutzen, müssen dafür aber Java-Archive im Klassenpfad einbinden. Die Referenzimplementierung befindet sich unter https://javaee.github.io/jsonp/. Am Einfachsten haben es Maven-Nutzer, sie binden in ihre POM folgende Abhängigkeiten ein:

<dependency>

  <groupId>javax.json</groupId>

  <artifactId>javax.json-api</artifactId>

  <version>1.1</version>

</dependency>

<dependency>

  <groupId>org.glassfish</groupId>

  <artifactId>javax.json</artifactId>

  <version>1.1</version>

</dependency>

Aufbauen von JSON-Objekten, Formatieren und Parsen

Der Typ JsonObject ist in der API zentral, denn er definiert ein hierarchisches Model mit den Schlüssel-Wertepaaren eines JSON-Objekts. Um ein JsonObject aufzubauen können wir über den JsonObjectBuilder gehen, oder wir lassen den Parser JsonReader aus einer String-Repräsentation ein JsonObject erzeugen. Zum formatierten Schreiben in einen Ausgabestrom können wir einen einfachen JsonWriter von der Json-Klasse holen – es geht aber noch einfacher über toString() – oder über die JsonWriterFactory arbeiten, falls wir eine hübsche eingerückte Ausgabe wünschen. Ein Beispiel:

Point p = new Point( 10, 20 );

JsonObjectBuilder objBuilder = Json.createObjectBuilder()

                                   .add( "x", p.x )

                                   .add( "y", p.y );

JsonObject jsonObj = objBuilder.build();


System.out.println( jsonObj );  // {"x":10,"y":20}


Json.createWriter( System.out ).write( jsonObj ); // {"x":10,"y":20}


Map<String, Boolean> config = new HashMap<>();

config.put( JsonGenerator.PRETTY_PRINTING, true );

JsonWriterFactory writerFactory = Json.createWriterFactory( config );


StringWriter out = new StringWriter();

writerFactory.createWriter( out ).write( jsonObj );

System.out.println( out );  // {\n    "x": 10, ...


JsonReader reader = Json.createReader( new StringReader( out.toString() ) );

System.out.println( reader.readObject().getInt( "x" ) ); // 10

Soll der assoziierte Wert ein Array sein, so wird dieser mit Json.createArrayBuilder().add(..).add(..) aufgebaut und gefüllt.

JSON-Streaming API

So wie es für XML eine Pull-API gibt, existiert diese auch für JSON-Dokumente; das ist von Vorteil, wenn die Daten sehr groß sind. Ein Beispiel zeigt das sehr gut:

URL url = new URL( "https://data.cityofnewyork.us/api/views/25th-nujf/rows.json?accessType=DOWNLOAD" );



try ( JsonParser parser = Json.createParser( url.openStream() ) ) {

  while ( parser.hasNext() ) {

    switch ( parser.next() ) {

      case KEY_NAME:

      case VALUE_STRING:

        System.out.println( parser.getString() );

        break;

      case VALUE_NUMBER:

        System.out.println( parser.getBigDecimal() );

        break;

      case VALUE_TRUE:

        System.out.println( true );

        break;

      case VALUE_FALSE:

        System.out.println( false );

        break;

      case VALUE_NULL:

      case START_ARRAY:

      case END_ARRAY:

      case START_OBJECT:

      case END_OBJECT:

        // Ignore

    }

  }

}

JAXB-Beans aus XML-Schema-Datei generieren

Da es für existierende XML-Dateien mühselig ist, die annotierten JavaBeans von Hand aufzubauen, gibt es einen Generator, genannt Java Architecture for XML Binding Compiler, kurz xjc. Er kann von der Kommandozeile, von Maven, aus einem Ant-Skript oder auch von Entwicklungsumgebungen aus aufgerufen werden. Er nimmt eine XML-Schema-Datei und generiert die Java-Klassen und eine ObjectFactory, die als – wie der Name schon sagt – Fabrik für die gemappten Objekte aus den XML-Elementen fungiert.

xjc aufrufen

Im ersten Schritt wechseln wir auf die Kommandozeile und testen entweder, ob das bin-Verzeichnis vom JDK im Suchpfad ist, oder wir wechseln in das bin-Verzeichnis, sodass wir xjc direkt aufrufen können, und folgende Ausgabe erscheint:

$ xjc -help

Verwendung: xjc [-options ...] <schema file/URL/dir/jar> ... [-b <bindinfo>] ...

Wenn dir angegeben wird, werden alle Schemadateien im Verzeichnis kompiliert.

Wenn jar angegeben wird, wird die /META-INF/sun-jaxb.episode-Binding-Datei kompiliert.

Optionen:

  -nv                :  Führt keine strikte Validierung der Eingabeschemas durch

  -extension         :  Lässt Herstellererweiterungen zu - Befolgt die

                        Kompatibilitätsregeln und App E.2 der JAXB-Spezifikation nicht strikt

  -b <file/dir>      :  Gibt externe Bindings-Dateien an (jede <file> muss ihre eigene Option -b haben)

                        Wenn ein Verzeichnis angegeben wird, wird **/*.xjb durchsucht

  -d <dir>           :  Generierte Dateien werden in diesem Verzeichnis gespeichert

  -p <pkg>           :  Gibt das Zielpackage an

  -httpproxy <proxy> :  set HTTP/HTTPS proxy. Format ist [user[:password]@]proxyHost:proxyPort

  -httpproxyfile <f> : Wird wie -httpproxy verwendet, verwendet jedoch das Argument in einer Datei zum Schutz des Kennwortes

  -classpath <arg>   :  Gibt an, wo die Benutzerklassendateien gefunden werden

  -catalog <file>    :  Gibt Katalogdateien zur Auflösung von externen Entity-Referenzen an

                        Unterstützt TR9401, XCatalog und OASIS-XML-Katalogformat.

  -readOnly          :  Generierte Dateien werden im schreibgeschützten Modus gelesen

  -npa               :  Unterdrückt die Generierung von Annotationen auf Packageebene (**/package-info.java)

  -no-header         :  Unterdrückt die Generierung eines Datei-Headers mit Zeitstempel

  -target (2.0|2.1)  :  Verhält sich wie XJC 2.0 oder 2.1 und generiert Code, der keine 2.2-Features verwendet.

  -encoding <encoding> :  Gibt Zeichencodierung für generierte Quelldateien an

  -enableIntrospection :  Aktiviert die ordnungsgemäße Generierung von booleschen Gettern/Settern zur Aktivierung von Bean Introspection-APIs

  -contentForWildcard  :  Generiert Contenteigenschaft für Typen mit mehreren von xs:any abgeleiteten Elementen

  -xmlschema         :  Behandelt Eingabe als W3C-XML-Schema (Standard)

  -relaxng           :  Behandelt Eingabe als RELAX NG (experimentell, nicht unterstützt)

  -relaxng-compact   :  Behandelt Eingabe als RELAX NG-Kompaktsyntax (experimentell, nicht unterstützt)

  -dtd               :  Behandelt Eingabe als XML DTD (experimentell, nicht unterstützt)

  -wsdl              :  Behandelt Eingabe als WSDL und kompiliert enthaltene Schemas (experimentell, nicht unterstützt)

  -verbose           :  Verwendet extra-verbose

  -quiet             :  Unterdrückt die Compilerausgabe

  -help              :  Zeigt diese Hilfemeldung an

  -version           :  Zeigt Versionsinformationen an

  -fullversion       :  Zeigt vollständige Versionsinformationen an







Erweiterungen:

  -Xinject-code      :  inject specified Java code fragments into the generated code

  -Xlocator          :  enable source location support for generated code

  -Xsync-methods     :  generate accessor methods with the 'synchronized' keyword

  -mark-generated    :  mark the generated code as @javax.annotation.Generated

  -episode <FILE>    :  generate the episode file for separate compilation

  -Xpropertyaccessors :  Use XmlAccessType PROPERTY instead of FIELD for generated classes

Eigentlich ist bis auf die Angabe der Schema-Quelle (aus einer Datei oder alternativ die URL) keine weitere Angabe nötig. Es ist aber praktisch, zwei Optionen zu setzen: -p bestimmt das Java-Paket für die generierten Klassen und -d das Ausgabeverzeichnis, in dem der Generator die erzeugten Dateien ablegt.

Bibelvers über eine Bibel-API beziehen

Für ein Beispiel wählen wir eine freie Bibel-API. Die URL sieht so aus:

http://api.preachingcentral.com/bible.php?passage=Jn3:16&version=schlachter

Die Angabe von version ist optional, und bestimmt im gesetzten Fall die Sprache bzw. die Bibel-Ausgabe. Die Seite http://www.4-14.org.uk/xml-bible-web-service-api zeigt den Einsatz der einfachen API, bei der ganz simpel in die URL das Buch, Kapitel und Vers steht. Für unser Beispiel sieht das Ergebnis so aus:

<?xml version="1.0" encoding="UTF-8"?>

<bible>

 <title>Jn3:16</title>

 <range>

  <request>Jn3:16</request>

  <result>John 3:16</result>

  <item>

   <bookname>John</bookname>

   <chapter>3</chapter>

   <verse>16</verse>

   <text>Denn Gott hat die Welt so geliebt, daß er seinen eingeborenen Sohn gab, damit jeder, der an ihn glaubt, nicht verloren gehe, sondern ewiges Leben habe.</text>

  </item>

 </range>

 <time>0.007</time>

</bible>

Für unser Beispiel wollen wir das XML-Dokument nicht von Hand auseinanderpflücken, sondern JAXB soll uns eine gefüllte JavaBean mit allen Informationen liefern. Leider gibt es für dieses einfache XML-Format kein XML-Schema, sodass wir uns mit einem Trick behelfen: wir lassen uns von einem Dienst aus der XML-Datei ein Schema generieren, sodass wir damit zu xjc gehen können. Es gibt im Internet einige freie XML-nach-Schema-Konverter, zum Beispiel http://www.xmlforasp.net/CodeBank/System_Xml_Schema/BuildSchema/BuildXMLSchema.aspx. Das XML-Dokument wird in das Textfeld kopiert und nach dem Knopfdruck „Generate Schema“ folgt:

<?xml version="1.0" encoding="utf-16"?>

<xsd:schema attributeFormDefault="unqualified" elementFormDefault="qualified" version="1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <xsd:element name="bible">

    <xsd:complexType>

      <xsd:sequence>

        <xsd:element name="title" type="xsd:string" />

        <xsd:element name="range">

          <xsd:complexType>

            <xsd:sequence>

              <xsd:element name="request" type="xsd:string" />

              <xsd:element name="result" type="xsd:string" />

              <xsd:element name="item">

                <xsd:complexType>

                  <xsd:sequence>

                    <xsd:element name="bookname" type="xsd:string" />

                    <xsd:element name="chapter" type="xsd:int" />

                    <xsd:element name="verse" type="xsd:int" />

                    <xsd:element name="text" type="xsd:string" />

                  </xsd:sequence>

                </xsd:complexType>

              </xsd:element>

            </xsd:sequence>

          </xsd:complexType>

        </xsd:element>

        <xsd:element name="time" type="xsd:decimal" />

      </xsd:sequence>

    </xsd:complexType>

  </xsd:element>

</xsd:schema>

Diese Ausgabe speichern wir in der Datei bible.xsd, damit sie von xjc verwendet werden kann.

Den Aufruf von xjc setzen wir in eine Batch-Datei:

PATH=%PATH%;C:\Program Files\Java\jdk-9\bin

xjc -d src\main\java -p com.tutego.insel.xml.jaxb.bible bible.xsd

Das Tool generiert alle Typen für den komplexen Typ aus dem XML-Schema, um eine Paket-Annotation festmachen zu können, und ObjectFactory, die einfache Fabrikmethoden enthält, um die gemappten Objekte aufbauen zu können. Die Ausgabe vom Programm ist:

…>xjc -d src\main\java -p com.tutego.insel.xml.jaxb.bible bible.xsd

Ein Schema wird geparst ...

Ein Schema wird kompiliert ...

com\tutego\insel\xml\jaxb\bible\Bible.java

com\tutego\insel\xml\jaxb\bible\ObjectFactory.java

TIPP: In Eclipse lässt sich xjc einfach aufrufen. Selektiere im Package-Explorer BIBLE.xsd, im Kontextmenü Generate • JAXB Classes…, dann wähle das Java-Projekt aus, anschließend Next. Im folgenden Dialog gib als Paket „com.tutego.insel.xml.jaxb.bible“ ein, dann klicke auf Finish. Wichtig! Damit xjc gefunden wird, muss das JDK aktiviert sein, nicht das JRE; nur das JDK bringt das Werkzeug xjc mit.

Dann kann ein Java-Programm den Service mit einer URL ansprechen und sich das Ergebnis-XML in eine JavaBean konvertieren lassen.

package com.tutego.insel.xml.jaxb;




import javax.xml.bind.JAXB;

import com.tutego.insel.xml.jaxb.bible.Bible;




public class BibleOnline {

  public static void main( String[] args ) {

    String url = "http://api.preachingcentral.com/bible.php?passage=Jn3:16&version=schlachter";

    Bible bible = JAXB.unmarshal( url, Bible.class );

    System.out.println( bible.getRange().getItem().getText() );

  }

}

 

WICHTIG Das JAXB-Modul muss in Java 9 über einen Schalter auf der Kommandozeile hinzugenommen werden: –add-modules java.se.ee.

Konflikte in der Schema-Datei *

Leider haben viele XML-Schemas ein Problem, sodass sie nicht direkt vom Schema-Compiler verarbeitet werden können. Ein Beispiel zeigt das Dilemma:

<container>
  <head><content title="Titel"/></head>
  <body><content doc="doc.txt"/></body>
 </container>

In der hierarchischen Struktur heißt das in <head> und <body> vorkommende XML-Element gleich, nämlich content. Die Schema-Datei kann widerspruchslos definieren, dass die beiden XML-Elemente gleich heißen, aber unterschiedliche Attribute erlauben, sozusagen das Head-Content und das Body-Content. Allein durch ihre Hierarchie, also dadurch, dass sie einmal unter head und einmal unter body liegen, sind sie eindeutig bestimmt. Der Schema-Compiler von Java bekommt aber Probleme, da er diese hierarchische Information in eine flache bringt. Er kann einfach eine Klasse Head und Body aufbauen, aber bei <content> steht er vor einem Problem. Da die Schema-Definitionen unterschiedlich sind, müssten zwei verschiedene Java-Klassen unter dem gleichen Namen Content generiert werden. Das geht nicht, und xjc bricht mit Fehlern ab.

Fehler dieser Art gibt es leider häufig, und sie sind der Grund, warum aus vielen Schemas nicht einfach JavaBeans generiert werden können. Erfolglos ohne weitere Einstellungen sind beispielsweise DocBook, Office Open XML, SVG, MathML und weitere. Doch was könnte die Lösung sein? xjc sieht Konfigurationsdateien vor, die das Mapping anpassen können. In diesen Mapping-Dokumenten identifiziert ein XPath-Ausdruck die problematische Stelle und gibt einen Substitutionstyp an. Die Dokumentation auf der JAXB-Homepage weist Interessierte in die richtige Richtung.

JAXB-Plugins

Auf der Webseite https://github.com/javaee/jaxb2-commons gibt es eine Reihe nützlicher zusätzlicher Plugins für JAXB. Zu ihnen gehören:

  • JAXB2 Basic Plugins: Diese Plugin-Sammlung besteht aus Equals (erzeugt die equals(…)-Methode, wobei der Gleichheitstest über ein Equals-Strategie-Objekt austauschbar ist), HashCode (erzeugt hashCode(), wobei auch hier die Berechnung des Hashwerts über ein Strategie-Objekt erfolgt), ToString (erzeugt toString(), auch wieder über ein externes ToStringStrategy-Objekt), Copyable (erzeugt copyTo(…), um Objektzustände in ein anderes typkompatibles Objekt zu kopieren, und auch clone(); arbeitet dabei mit CopyStrategie), Mergeable (realisiert mergeFrom(…)-Methoden mit MergeStrategy-Objekten, die Zustände eines anderen Objekts mit den eigenen vereinen), JAXBIndex (generiert eine index-Datei, die alle generierten Schema-Klassen auflistet), Inheritance und AutoInheritance (erlauben JAXB-Beans, globale Elemente oder komplexe Typen von gewünschten Basisklassen zu erben oder Schnittstellen zu implementieren), Wildcard (spezifiziert, wie sich das Wildcard-Property verhalten soll), Setters (etwas andere Setter-Implementierung bei Sammlungen) und Simplify Plugin (komplexe Properties in Einzel-Properties aufspalten).
  • Value-Constructor Plugin: Jede JavaBean bekommt von xjc nur einen Standard-Konstruktor. Das Plugin gibt einen weiteren Konstruktor hinzu, der alle Attribute direkt initialisiert.
  • Default Value Plugin: Ein XML-Schema kann mit defaultValue vordefinierte Initialbelegungen für Attribute angeben. xjc ignoriert diese. Das Plugin wertet diese Vorbelegungen aus und initialisiert die Attribute der JavaBean gemäß den Werten.
  • Fluent API Plugin: Anstatt eine Bean nur mit Setter-Aufrufen zu initialisieren, etwa eine Person mit setName(…) und setAge(…), generiert das Fluent API Plugin kaskadierbare Methoden, sodass im Beispiel unserer Person nur new Person().withName(…).withAge(…) zu schreiben ist.

Parsen und Formatieren von Datumszeitwerten der Java Date Time API

Alle temporalen Typen überschreiben standardmäßig die toString()-Methode und liefern eine standardisierte Ausgabe. Weiterhin lassen sich die temporalen Typen auch bei String.format(…) und dem java.util.Formatter einsetzen.

format(…)-Methode

Daneben bieten die Typen eine format(…)-Methode, der ein Formatierungsobjekt übergeben wird, sodass individuelle Ausgaben nach einem Muster möglich sind.

Klasse Formatierungsmethode
LocalDateTime format(DateTimeFormatter formatter)
LocalDate format(DateTimeFormatter formatter)
LocalTime format(DateTimeFormatter formatter)
ZonedDateTime format(DateTimeFormatter formatter)
OffsetDateTime format(DateTimeFormatter formatter)

Temporale Klassen nutzen den gleiche Parametertyp DateTimeFormatter

Die format(…)-Methode nimmt einen DateTimeFormatter an, der die Ausgabe beschreibt. Wie kommen wir an einen DateTimeFormatter? Die Klasse hat keinen öffentlichen Konstruktor, sondern Konstanten und statische Fabrikmethoden. Über drei Varianten kommen wir zum konkreten Objekt:

  • Es gibt vordefinierte Konstanten für standardisierte ISO/RFC-Ausgaben,
  • Methoden für vordefinierte lang/kurz-Formate (auch lokalisiert) und
  • Methoden für komplett selbst zusammengestellte Ausgaben.

Die API-Dokumentation zählt die Konstanten mit ihren Formaten aus, Beispiele sind ISO_LOCAL_DATE oder ISO_ZONED_DATE_TIME. Wichtig zu bedenken ist, dass der temporale Typ die Felder auch haben muss! Ein ISO_ZONED_DATE_TIME ist zum Beispiel bei einem LocalDate nicht möglich.

Praktischer sind die statischen ofLocalizedXXX(…)-Methoden, die eine Aufzählung FormatStyle (FULL, LONG, MEDIUM, SHORT) annehmen:

  • DateTimeFormatter ofLocalizedDate(FormatStyle dateStyle)
  • DateTimeFormatter ofLocalizedDateTime(FormatStyle dateTimeStyle)
  • DateTimeFormatter ofLocalizedDateTime(FormatStyle dateStyle, FormatStyle timeStyle)
  • DateTimeFormatter ofLocalizedTime(FormatStyle timeStyle)

Einen so aufgebauten DateTimeFormatter lässt sich Zusatzinformation geben mit diversen withXXX(…)-Methoden, etwa withLocale(Locale locale) oder withZone(ZoneId zone).

Beispiel

LocalDateTime now = LocalDateTime.now();

out.println( now.format( BASIC_ISO_DATE ) );

out.println( now.format( ISO_LOCAL_DATE_TIME ) );

out.println( now.format( ofLocalizedDate( SHORT ) ) );

out.println( now.format( ofLocalizedDateTime( MEDIUM ) ) );

out.println( now.format( ofLocalizedDateTime( FULL ).withLocale( FRANCE )

                                                    .withZone( ZoneId.of( "America/Cayenne" ) ) ) );

Die Ausgaben sehen so aus:

20170616

2017-06-16T20:39:11.2114379

16.06.17

16.06.2017, 20:39:11

vendredi 16 juin 2017 à 20:39:11 heure de la Guyane française

Eine völlig flexible Ausgabe ermöglicht ein Muster.

Beispiel

LocalDate now = LocalDate.now();
System.out.println( now );          // 2014-03-21
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( „d. MMMM yyyy“ );
String nowAsString = now.format( formatter );
System.out.println( nowAsString );  // 21. März 2014
LocalDate nowAgain = LocalDate.parse( nowAsString, formatter );
System.out.println( nowAgain );     // 2014-03-21

Die API-Dokumentation zu DateTimeFormatter zeigt einige vordefinierte Formatierungsobjekte und listet alle Formatspezifizierer auf.

parse(…)-Methode

Neben dem Formatieren bieten die Typen auch eine statische parse(…)-Methode, die einmal mit einem String-Parameter das Format erwartet, was toString() liefert, und eine Version mit zwei Parametern, wobei dann ein Formatierungsobjekt erlaubt ist, um genau das Format anzugeben, nach dem geparst werden soll.

Asnychrones Programmieren mit CompletableFuture (CompletionStage)

So schöne an Future-Objekten ist die Abarbeitung im Hintergrund und die Möglichkeit, später abzufragen, ob das Ergebnis schon da ist. Allerdings fehlt der Future-Schnittstelle eine Methode, automatisch nach der Fertigstellung einen Folgeauftrag abzuarbeiten. Dafür bietet die Java-Bibliothek eine spezielle Unterklasse CompletableFuture. Die Klasse implementiert die Schnittstelle CompletionStage, die vermutlich die größte Anzahl Operationen in der gesamten Java SE hat. Der Typname drückt aus, das es um die Fertigstellung (engl. completion) von Abschnitten (engl. stage) geht.

Ein Beispiel für einen trinkfesten mutigen Piraten:

package com.tutego.insel.concurrent;




import java.time.LocalTime;

import java.util.concurrent.CompletableFuture;

import java.util.concurrent.TimeUnit;

import java.util.logging.Logger;




class Pirate {




  public static void main( String[] arg ) throws Throwable {




    String result = CompletableFuture.supplyAsync( Pirate::newName )

                                     .thenApply( Pirate::swears )

                                     .thenCombine( drinkRum(), Pirate::combinePiratAndDrinks )

                                     .thenCombine( drinkRum(), Pirate::combinePiratAndDrinks )

                                     .get();

    System.out.println( result ); // Pirat Guybrush flucht und trinkt dann 10 Flaschen Rum und trinkt dann 11 Flaschen Rum

  }




  static String newName() {

    Logger.getGlobal().info( "" + Thread.currentThread() );

    return "Pirat Guybrush";

  }




  static String swears( String pirate ) {

    Logger.getGlobal().info( "" + Thread.currentThread() );

    return pirate + " flucht";

  }




  static CompletableFuture<Integer> drinkRum() {

    Logger.getGlobal().info( "" + Thread.currentThread() );

    try { TimeUnit.SECONDS.sleep( 1 ); } catch ( Exception e ) { }

    return CompletableFuture.supplyAsync( () -> LocalTime.now().getSecond() );

  }




  static String combinePiratAndDrinks( String pirat, int bottlesOfRum ) {

    Logger.getGlobal().info( "" + Thread.currentThread() );

    return pirat + " und trinkt dann " + bottlesOfRum + " Flaschen Rum";

  }

}

Die Ausgabe ist:

Juni 15, 2017 10:41:56 NACHM. com.tutego.insel.thread.concurrent.Pirate drinkRum

INFORMATION: Thread[main,5,main]

Juni 15, 2017 10:41:56 NACHM. com.tutego.insel.thread.concurrent.Pirate newName

INFORMATION: Thread[ForkJoinPool.commonPool-worker-1,5,main]

Juni 15, 2017 10:41:56 NACHM. com.tutego.insel.thread.concurrent.Pirate swears

INFORMATION: Thread[ForkJoinPool.commonPool-worker-1,5,main]

Juni 15, 2017 10:41:57 NACHM. com.tutego.insel.thread.concurrent.Pirate combinePiratAndDrinks

INFORMATION: Thread[main,5,main]

Juni 15, 2017 10:41:57 NACHM. com.tutego.insel.thread.concurrent.Pirate drinkRum

INFORMATION: Thread[main,5,main]

Juni 15, 2017 10:41:58 NACHM. com.tutego.insel.thread.concurrent.Pirate combinePiratAndDrinks

INFORMATION: Thread[main,5,main]

Pirat Guybrush flucht und trinkt dann 57 Flaschen Rum und trinkt dann 58 Flaschen Rum

Zum Programm: Zunächst muss die Kette von Abschnitten aufgebaut werden. Das kann entweder mit dem Standardkonstruktor geschehen, oder mit statischen Methoden. In unserem Fall nutzen wir supplyAsync(Supplier<U> supplier). Die Methode nimmt sich einen freien Thread aus dem ForkJoinPool.commonPool() und lässt den Thread den supplier abarbeiten. Das Ergebnis ist über die Rückgabe, einem CompletableFuture, abrufbar. Als nächsten wenden wir thenApply(Function<? super T,? extends U> fn) an, die vergleichbar ist mit einer map(…)-Operation eines Streams. Interessant wird es bei thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn); sie verbindet das Ergebnis der eigenen CompletionStage über eine Funktion mit einer anderen CompletionStage, die wir in unserem Fall auch wieder mit supplyAsync(…) aufbauen. So kombinieren wir zwei unabhängige CompletionStage miteinander und synchronisieren das Ergebnis. Wir können das gut an der Ausgabe auslesen, dass drinkRum ganz am Anfang schon ausgeführt wird, und zwar vom Thread[main,5,main], nicht vom ForkJoinPool, weil es unabhängig von den anderen läuft.

HTTP Client API in Java 9

Der HTTP-Standard ist schon relativ alt und die Ursprünge wurden Anfang der 1990er Jahre gelegt. Offiziell wurde die Version 1.0 im Jahr 1996 verabschiedet, also etwa zu gleichen Zeit, als Java 1.0 erschien. 2015 wurde HTTP/2 verabschiedet, und viele Verbesserungen eingeführt. Die Java-Klassen rund um HTTP wurden allerdings nicht aktualisiert, sodass quelloffene Bibliothek die Lücken füllen.

Statt für Java 9 die existierenden HTTP-Klassen wie HttpURLConnection zu erweitern, hat Oracle ein ganz neues Paket eingeführt. Allerdings ist das noch nicht wirklich „freigeschaltet“, sondern ist im „Brutkasten“ (engl. incubator) und ein Modul jdk.incubator.httpclient muss bewusst hinzugenommen werden – in Java 10 soll es dann umbenannt werden, wobei sich die API noch ändern kann.

Die wesentlichen Typen sind:

  • HttpClient
  • HttpRequest, HttpResponse
  • WebSocket

Schauen wir uns ein Beispiel an:

try {

  URI uri = new URI( "https://tutego.de/" );

  HttpRequest httpRequest = HttpRequest.newBuilder().uri( uri ).GET().build();

  HttpResponse<String> response = HttpClient.newHttpClient().send(

                                    httpRequest, HttpResponse.BodyHandler.asString() );

  System.out.println( response.body() );

}

catch ( URISyntaxException | IOException | InterruptedException e ) {

  e.printStackTrace();

}

Um das Programm unter Java 9 laufen zu lassen ist unerlässlich –add-modules jdk.incubator.httpclient auf der Kommandozeile aufzunehmen.

Die Methoden von HttpRequest sind allesamt abstrakt:

  • Optional<HttpRequest.BodyProcessor> bodyProcessor()
  • Duration duration()
  • boolean expectContinue()
  • HttpHeaders headers()
  • String method()
  • Builder newBuilder()
  • Builder newBuilder(URI uri)
  • BodyProcessor noBody()
  • URI uri()
  • Version version()

Führt der HttpClient die Anfrage aus, bekommen wir ein HttpResponse; die Klasse hat folgende (abstrakte) Methoden:

  • T body()
  • int statusCode()
  • HttpRequest finalRequest()
  • HttpHeaders headers()
  • HttpRequest request()
  • SSLParameters sslParameters()
  • CompletableFuture<HttpHeaders> trailers()
  • URI uri()
  • Version version()

Was die Methode body() für einen Typ liefert bestimmt die Parametrisierung von HttpResponse<T>, und die ergibt sich aus dem übergebenen BodyHandler<T>  der HttpClient-Methode send(…):

  • HttpResponse<T> send(HttpRequest req, HttpResponse.BodyHandler<T> responseBodyHandler)

Der BodyHandler ist eine funktionale Schnittstelle mit ein paar statischen Methoden für vordefinierte Implementierungen:

  • BodyHandler<String> asString()
  • BodyHandler<String> asString(Charset charset)
  • BodyHandler<byte[]> asByteArray()
  • BodyHandler<Void> asByteArrayConsumer(Consumer<Optional<byte[]>> consumer)
  • BodyHandler<Path> asFile(Path file)
  • BodyHandler<Path> asFile(Path file, OpenOption… openOptions)
  • BodyHandler<Path> asFileDownload(Path directory, OpenOption… openOptions)
  • <U> HttpResponse.BodyHandler<U> discard(U value)

Insel-Update: Aufräumen mit finalize()

Wenn die automatische Speicherbereinigung feststellt, dass es keine Referenz mehr auf ein bestimmtes Objekt gibt, so ruft sie automatisch die besondere Methode finalize() auf diesem Objekt auf. Danach kann die automatische Speicherbereinigung das Objekt entfernen. Wir können diese Methode für eigene Aufräumarbeiten überschreiben, die Finalizer genannt wird (ein Finalizer hat nichts mit dem finally-Block einer Exception-Behandlung zu tun).

Seit Java 9 ist die Methode deprecated[1], um Entwickler zu ermutigen, auf diese Methode zu verzichten, obwohl sie natürlich weiterhin von der JVM aufgerufen wird. Es gibt mehrere Probleme. Einige Entwickler verstehen die Arbeitsweise der Methode nicht richtig und denken, sie wird immer aufgerufen. Doch hat die virtuelle Maschine Fantastillionen Megabyte an Speicher zur Verfügung und wird dann beendet, gibt sie den Heap-Speicher als Ganzes dem Betriebssystem zurück. In so einem Fall gibt es keinen Grund für eine automatische Speicherbereinigung als Grabträger und Folglich kein Aufruf von finalize(). Und wann genau der Garbage-Collector in Aktion tritt, ist auch nicht vorhersehbar, sodass im Gegensatz zu C++ in Java keine Aussage über den Zeitpunkt möglich ist, zu dem das Laufzeitsystem finalize() aufruft – alles ist vollständig nicht-deterministisch und von der Implementierung der automatischen Speicherbereinigung abhängig. Üblicherweise werden Objekte mit finalize() von einem Extra-Garbage-Collector behandelt, und der arbeitet langsamer als der normale GC, was somit ein Nachteil ist.

Sprachenvergleich

Einen Destruktor, der wie in C++ am Ende eines Gültigkeitsbereichs einer Variablen aufgerufen wird, gibt es in Java nicht.

 

class java.lang.Object

  • protected void finalize() throwsThrowable
    Die Methode wird von der automatischen Speicherbereinigung aufgerufen, wenn es auf dieses Objekt keinen Verweis mehr gibt. Die Methode ist geschützt, weil sie von uns nicht aufgerufen wird. Auch wenn wir die Methode überschreiben, sollten wir die Sichtbarkeit nicht erhöhen, also nicht public

Einmal Finalizer, vielleicht mehrmals die automatische Speicherbereinigung

Objekte von Klassen, die eine finalize()-Methode besitzen, kann Oracles JVM nicht so schnell erzeugen und entfernen wie Klassen ohne finalize(). Das liegt auch daran, dass die automatische Speicherbereinigung vielleicht mehrmals laufen muss, um das Objekt zu löschen. Es gilt zwar, dass der Garbage-Collector aus dem Grund finalize() aufruft, weil das Objekt nicht mehr benötigt wird, es kann aber sein, dass die finalize()-Methode die this-Referenz nach außen gibt, sodass das Objekt wegen einer bestehenden Referenz nicht gelöscht werden kann und so zurück von den Toten geholt wird. Das Objekt wird zwar irgendwann entfernt, aber der Finalizer läuft nur einmal und nicht immer pro GC-Versuch.[2]

Löst eine Anweisung in finalize() eine Ausnahme aus, so wird diese ignoriert. Das bedeutet aber, dass die Finalisierung des Objekts stehen bleibt. Die automatische Speicherbereinigung beeinflusst das in ihrer Arbeit aber nicht.

super.finalize()

Überschreiben wir in einer Unterklasse finalize(), dann müssen wir auch gewährleisten, dass die Methode finalize() der Oberklasse aufgerufen wird. So besitzt zum Beispiel die Klasse Font ein finalize(), das durch eine eigene Implementierung nicht verschwinden darf. Wir müssen daher in unserer Implementierung super.finalize() aufrufen (es wäre gut, wenn der Compiler das wie beim Konstruktoraufruf immer automatisch machen würde). Leere finalize()-Methoden ergeben im Allgemeinen keinen Sinn, es sei denn, das finalize() der Oberklasse soll explizit übergangen werden:

@Override protected void finalize() throws Throwable {
try {
// …
}
finally {
super.finalize();
}
}

Der Block vom finally wird immer ausgeführt, auch wenn es im oberen Teil eine Ausnahme gab.

Die Methode von Hand aufzurufen, ist ebenfalls keine gute Idee, denn das kann zu Problemen führen, wenn der GC-Thread die Methode auch gerade aufruft. Um das Aufrufen von außen einzuschränken, sollte die Sichtbarkeit von protected bleiben und nicht erhöht werden.

Hinweis

Da beim Programmende vielleicht nicht alle finalize()-Methoden abgearbeitet wurden, haben die Entwickler schon früh einen Methodenaufruf System.runFinalizersOnExit(true); vorgesehen. Mittlerweile ist die Methode veraltet und sollte auf keinen Fall mehr aufgerufen werden. Die API-Dokumentation erklärt:

„It may result in finalizers being called on live objects while other threads are concurrently manipulating those objects, resulting in erratic behavior or deadlock.“

Dazu auch Joshua Bloch, Autor des ausgezeichneten Buchs „Effective Java Programming Language Guide“:

„Never call System.runFinalizersOnExit or Runtime.runFinalizersOnExit for any reason: they are among the most dangerous methods in the Java libraries.“

Gültige Alternativen

Gedacht war die überschriebene Methode finalize() um wichtige Ressourcen zur Not freizugeben, etwa File-Handles via close() oder Grafikkontexte des Betriebssystems, wenn der Entwickler das vergessen hat. Alle diese Freigaben müssten eigentlich vom Entwickler angestoßen werden, und finalize() ist nur ein Helfer, der rettend eingreifen kann. Doch da die automatische Speicherbereinigung finalize() nur dann aufruft, wenn sie tote Objekte freigeben möchte, dürfen wir uns nicht auf die Ausführung verlassen. Gehen zum Beispiel die File-Handles aus, wird der Garbage-Collector nicht aktiv; es erfolgen keine finalize()-Aufrufe, und nicht mehr erreichbare, aber noch nicht weggeräumte Objekte belegen weiter die knappen File-Handles. Es muss also ein Mechanismus her, der korrekt ist und und immer funktioniert. Hier gibt es zwei Ansätze:

  1. try-mit-Ressourcen ist ein eleganter Weg, damit auf Ressourcen die close()-Methode aufgerufen wird. Nachteil ist, dass die genutzte Ressource das selbst nicht weiß, ob der Nutzer sie mit close()
  2. Die unter Java 9 eingeführte Klasse lang.ref.Cleaner hilft beim Aufräumen, dazu später mehr in diesem Kapitel.

 

[1]      https://bugs.openjdk.java.net/browse/JDK-8165641

[2] Einige Hintergründe erfährt der Leser unter http://www.iecc.com/gclist/GC-lang.html#Finalization.

Versionskennungen aufbauen, parsen, vergleichen

In Java 9 wurde die Kennung für die Java-Version standardisiert. Wo früher von zum Beispiel 1.9.0_31-b08 die Rede war, heißt es heute 9.1.4+8; die 1. ist also verständig verschwunden. Auch eigene Programme können Versionskennungen nutzen und die Java SE stellt eine Klasse bereit, mit der sich zum Beispiel Versionen vergleichen lassen. Details zu den sogenannten semantischen Versionierung liefert http://semver.org/lang/de/.

Versionskennung

Ein Versionskennung hat die Form $MAJOR.$MINOR.$SECURITY. Das Schema besteht aus drei Teilen, die durch Punkte voneinander getrennt sind:

  1. Hautversion (engl. major version). Wird immer bei zentralen Änderungen und Updates um eine Stelle erhöht. Oft verbunden mit inkompatiblen API-Änderungen. Dann wird die Unterversion zurück auf 0 gesetzt.
  2. Unterversion (engl. minor verson). Wird erhöht bei kleineren Änderungen, wie Bug-Fixes. Das Release selbst bleibt rückwärtskompatibel.
  3. Sicherheitsrelease/Patch. Wird erhöht nach kritischen Sicherheitsupdates. Wichtig: Die Release-Nummer wird nicht auf 0 zurückgesetzt, wenn die Unterversion erhöht wird. Ein hoher Zähler der Sicherheitsreleases weist auf viele Änderungen hin, unabhängig von der Unterversion.

Die Versionen sind rein numerisch und der Gesamtstring matcht auf den regulären Ausdruck [1-9][0-9]*((\.0)*\.[1-9][0-9]*)*. Die Kennung endet nicht mit 0 – so wird $SECURITY weggelassen, wenn sie 0 ist und $MINOR wird nicht gesetzt, wenn $MINOR und $SECURITY beide 0 sind.

Versionsstring

Ein Versionsstring ist eine Versionskennung mit einem optionalen Zusatz von Informationen wie einer Vorversion (engl. pre release) oder Build-Informationen. Den Aufbau erklärt die API-Dokumentation an der Klasse Runtime.Version.

Version-API

Die Runtime-Klasse hat unter Java 9 eine innere Klasse Version bekommen, die so einen Versionsstring aufbauen und parsen kann. Die Version der aktuellen Laufzeitumgebung liefert die statische Methode version().

Beispiel

Gib alle Informationen über den Versionsstring aus:

System.out.println( Runtime.version() ); // 9-ea+159

System.out.println( Runtime.version().major() ); // 9

System.out.println( Runtime.version().minor() ); // 0

System.out.println( Runtime.version().security() ); // 0

System.out.println( Runtime.version().pre() ); // ea

System.out.println( Runtime.version().build() ); // 159

System.out.println( Runtime.version().optional() ); // Optional.empty

Eigene Version-Objekte lassen sich aus einem Versionsstring aufbauen mit der einzigen statischen Methode der Klasse Version, und zwar parse(String).

Beispiel

Version version = Version.parse( "9.2.4+45" );

System.out.println( version.version() );  // [9, 2, 4]

An dem Beispiel ist auch version() abzulesen, die eine numerische Liste mit Versionsnummern liefert.

Neben diesen Abfragemethoden kommen weitere Methoden hinzu. Zunächst überschreibt Version die Object-Methoden equals(…), hashCode() und toString(). Und da Version die Schnittstelle Comparable<Runtime.Version> implementiert, hat die Klasse eine Methode compareTo(Runtime.Version), was die Versionsnummern in eine Ordnung bringt. Zusätzlich gibt es compareToIgnoreOptional(Runtime.Version) und equalsIgnoreOptional(Object), was so etwas wie Build-Informationen ignoriert.

Java 9 wieder verschoben um 2 Monate

Auf den vermutlich 21. September 2017.  Aus der E-Mail http://mail.openjdk.java.net/pipermail/jdk9-dev/2017-May/005864.html von Mark Reinhold:

As you probably know by now, the JCP Executive Committee (EC) recently voted [1] not to approve JSR 376, the Java Platform Module System [2], for the next stage of the process.

This vote does not mean that JSR 376 is dead, nor that Jigsaw has been rejected. It only means that the EC raised a number of concerns that it wanted the JSR 376 Expert Group (EG) to address. The JCP rules give the EG thirty days, until 7 June, to submit a revised specification for a second EC vote, which will end no later than 26 June [3].

The JSR 376 EG held a series of conference calls over the past two weeks in order discuss the EC’s concerns [4]. The net impact of those meetings on JDK 9 itself was to clarify the specification of the module system’s resolution algorithm, work on which had already begun, and to add one five-line method to the module-system API. These changes, together with additional clarifications to the JSR 376 and JSR 379 (Java SE 9) [5] Specifications, will hopefully address the EC’s concerns.

In order to be ready for all possible outcomes I suggest that here in the JDK 9 Project we continue to work toward the current goal of producing an initial Release Candidate build on 22 June [6], but adjust the GA date in order to accommodate the additional time required to move through the JCP process. To be specific, I propose that we move the GA date out by eight weeks, from 27 July to 21 September.

Comments on this proposal from JDK 9 Committers are welcome, as are reasoned objections. If no such objections are raised by 23:00 UTC next Tuesday, 6 June, or if they’re raised and satisfactorily answered, then per the JEP 2.0 process proposal [7] this will be the new schedule for JDK 9.