Module entwickeln und einbinden

Das JPMS (Java Platform Module System), auch unter dem Projektnamen Jigsaw bekannt, ist eine der größten Neuerungen in Java 9. Im Mittelpunkt steht die starke Kapselung: Implementierungsdetails kann ein Modul geheim gehalten. Selbst Hilfscode innerhalb des Moduls, auch wenn er öffentlich ist, darf nicht nach außen dringen. Zweitens kommt eine Abstraktion von Verhalten über Schnittstellen hinzu, die interne Klassen aus dem Modul implementieren können, wobei dem Nutzer die konkreten Klassen nicht bekannt sind. Als dritten Punkt machen explizite Abhängigkeiten die Interaktion mit anderen Modulen klar. Eine grafische Darstellung hilft auch bei großen Architekturen, die Übersicht über Nutzungsbeziehungen zu behalten.

Wer sieht wen

Klassen, Pakete und Module lassen sich als Container mit unterschiedlichen Sichtbarkeiten sehen:

· Ein Typ, sei es Klasse oder Schnittstelle, enthält Attribute und Methoden

· Ein Paket enthält Typen.

· Ein Modul enthält Pakete.

· Private Eigenschaften in einem Typ sind nicht in anderen Typen sichtbar.

· Nicht öffentliche Typen sind in anderen Paketen nicht sichtbar.

· Nicht exportierte Pakete sind außerhalb eines Moduls nicht sichtbar.

Ein Modul ist definiert

1. durch einem Namen,

2. durch die Angabe, was es exportiert möchte und

3. welches Modul es zur Arbeit selbst benötigt.

Interessant ist der zweite Aspekt, also dass ein Modul etwas exportiert. Wenn nichts exportiert wird, ist auch nichts sichtbar nach außen. Alles, was Außenstehende sehen sollen, muss in der Modulbeschreibung aufgeführt sein – nicht alle öffentlichen Typen des Moduls sind standardmäßig öffentlich, dann wäre das kein Fortschritt zu JAR-Dateien. Mit dem neuen Modulsystem haben wir also eine ganz andere Sichtbarkeit. Aus der Viererbande public, private, paketsichtbar, protected bekommt public eine viel feinere Abstufung. Denn was public ist bestimmt das Modul, und das sind

– Typen, die das Modul für alle exportiert,

– Typen für explizit aufgezählte Module,

– alle Typen im gleichen Modul.

Der Compiler und die JVM achten auf die Einhaltung der Sichtbarkeit, und auch Tricks mit Reflection sind nicht mehr möglich, wenn ein Modul keine Freigabe erteilt hat.

Modultypen

Wir wollen uns in dem Abschnitt intensiver mit drei Modultypen beschäftigen. Wenn wir neue Module schreiben, dann sind das benannte Module. Daneben gibt es aus Kompatibilitätsgründen automatische Module und unbenannte Module, mit denen wir vorhandene JAR-Dateien einbringen können. Die Bibliothek der Java SE ist selbst in Module unterteilt, wir nennen sie Plattform-Module.

Die Laufzeitumgebung zeigt mit einem Schalter –list-modules alle Plattform-Module an.

Beispiel: Liste die ca. 70 Module auf:

$ java –list-modules

java.activation@9

java.base@9

java.compiler@9

oracle.desktop@9

oracle.net@9

Im Ordner C:\Program Files\Java\jdk-9\jmods liegen JMOD-Dateien.

Plattform-Module und JMOD-Beispiel

Das Kommandozeilenwerkzeug jmod zeigt an, was ein Modul exportiert und benötigt. Nehmen wir die JDBC-API für Datenbankverbindungen als Beispiel; die Typen sind in einem eigenen Modul mit den Namen java.sql.

C:\Program Files\Java\jdk-9\bin>jmod describe ..\jmods\java.sql.jmod

java.sql@9

exports java.sql

exports javax.sql

exports javax.transaction.xa

requires java.base mandated

requires java.logging transitive

requires java.xml transitive

uses java.sql.Driver

platform windows-amd64

Wir können ablesen:

· den Namen

· die Pakete, die das Modul exportiert: java.sql, javax.sql und javax.transation.xa

· die Module, die java.sql benötigt: java.base ist hier immer drin, dazu kommen java.logging und java.xml

Die Meldung mit „uses“ steht im Zusammenhang mit dem Service-Locator – wir können das vorerst ignorieren. Die Kennung über die Plattform (windows-amd64) schreibt jmod mit hinein, es ist die Belegung der System-Property os.arch auf dem Build-Server.

Verbotene Plattformeigenschaften nutzen, –add-exports

Als Sun von vielen Jahren mit der Entwicklung der Java-Bibliotheken begann, kamen viele interne Hilfsklassen mit in die Bibliothek. Viele beginnen mit den Paketpräfixen com.sun und sun. Die Typen wurden immer als interne Typen kommuniziert, doch bei einigen Entwicklern war die Neugierde und das Interesse so groß, dass die Warnungen von Sun/Oracle ignoriert wurden. In Java 9 kommt der große Knall, da public nicht mehr automatisch public für alle Klassen außerhalb des Moduls ist; die internen Klassen werden nicht mehr exportiert, sind also nicht mehr benutzbar. Es kommt zu einem Compilerfehler, wie in folgendem Beispiel:

public class ShowRuntimeArguments {

public static void main( String[] args ) throws Exception {

System.out.println( java.util.Arrays.toString( jdk.internal.misc.VM.getRuntimeArguments() ) );

}

}

Unser Programm greift auf die VM-Klasse zurück, um die eigentliche Belegung der Kommandozeile zu erfragen. In der main(String[] args)-Methode sind in args keine VM-Argumente enthalten.

Übersetzen wir das Programm, gibt es einen Compilerfehler (nicht bei einem Java 8-Compiler):

$ javac ShowRuntimeArguments.java

ShowRuntimeArguments.java:3: error: package jdk.internal.misc is not visible

System.out.println( java.util.Arrays.toString( jdk.internal.misc.VM.getRuntimeArguments() ) );

^

(package jdk.internal.misc is declared in module java.base, which does not export it to the unnamed module)

1 error

The module java.base does not export the package jdk.internal.misc., so the type jdk.internal.misc.Unsafe is not accessible – as a consequence compilation fails.

Das Problem dokumentiert der Compiler. Es ist dadurch zu lösen, indem mit dem Schalter –add-exports aus dem Modul java.base das Paket jdk.internal.misc unserer Klasse bereitgestellt wird. Die Angabe ist für den Compiler und für die Laufzeitumgebung zu setzen:

$ javac –add-exports java.base/jdk.internal.misc=ALL-UNNAMED ShowRuntimeArguments.java

$ java ShowRuntimeArguments

Exception in thread "main" java.lang.IllegalAccessError: class ShowRuntimeArguments (in unnamed module @0x77afea7d) cannot access class jdk.internal.misc.VM (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @0x77afea7d

at ShowRuntimeArguments.main(ShowRuntimeArguments.java:3)

$ java –add-exports java.base/jdk.internal.misc=ALL-UNNAMED ShowRuntimeArguments

[–add-exports=java.base/jdk.internal.misc=ALL-UNNAMED]

Wir sehen die Ausgabe, das Programm funktioniert.

Eine Angabe wie java.base/jdk.internal.misc, bei der vorne das Modul steht und hinter dem / der Paketname, ist oft ab Java 9 anzutreffen. Hinter dem Gleichheitszeichen steht entweder unser Paket, welches die Typen in jdk.internal.misc sehen kann, oder – wie in unserem Fall – ALL-UNNAMED.

jdeps

Hätten wir das Programm schon erfolgreich unter Java 8 übersetzt, würde es zur Laufzeit ebenfalls knallen. Da es nun sehr viel Programmcode gibt, haben die Java-Entwickler bei Oracle das Kommandozeilenprogramm jdeps entwickelt. Es meldet, wenn interne Typen im Programm vorkommen:

$ jdeps ShowRuntimeArguments.class

ShowRuntimeArguments.class -> java.base

<unnamed> -> java.io java.base

<unnamed> -> java.lang java.base

<unnamed> -> java.util java.base

<unnamed> -> jdk.internal.misc JDK internal API (java.base)

Die Meldung „JDK internal API“ bereitet uns darauf vor, dass es gleich Ärger geben wird.

So kann relativ leicht eine große Codebasis untersucht werden und Entwicker können proaktiv den Stellen auf den Grund gehen, die problematische Abhängigkeiten haben.

Plattformmodule einbinden, –add-modules und –add-opens

Jedes Java SE-Projekt basiert auf dem Modul java.se, was diverse Modulabhängigkeiten nach sich zieht.

<pic: java.se-graph.png, „Modulabhängigkeiten von java.se“>

Diverse Module sind nicht Teil vom Modul Java SE, unter anderem sind das das Java Activation Framework, CORBA, Transaction-API, JAXB, Web-Services und interne Module, die mit jdk beginnen.

Starten wir ein Programm mit Bezug zu einem dieser Bibliotheken gibt es einen Fehler. Zunächst zum Programm, das ein Objekt automatisch in XML konvertieren soll:

public class Person {

public String name = "Chris";

public static void main( String[] args ) {

javax.xml.bind.JAXB.marshal( new Person(), System.out );

}

}

Ausgeführt auf der Kommandozeile folgt ein Fehler:

$ java Person

Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/JAXB

at Person.main(Person.java:6)

Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXB

at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source)

at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Unknown Source)

at java.base/java.lang.ClassLoader.loadClass(Unknown Source)

… 1 more

Wir müssen das Modul java.xml.bind (oder auch das „Über“-Modul java.se.ee) mit angeben; dafür dient der Schalter –add-modules.

$ java –add-modules java.xml.bind Person

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

<person>

<name>Chris</name>

</person>

Öffnen für Reflection

Jetzt gibt es allerdings ein anderes Problem, was auffällt, wenn wir einen anderen Typ in XML umwandeln wollen:

public class Today {

public static void main( String[] args ) {

javax.xml.bind.JAXB.marshal( new java.util.Date(), System.out );

}

}

Auf der Kommandozeile zeigt sich:

$ java –add-modules java.xml.bind Today

Exception in thread "main" javax.xml.bind.DataBindingException: javax.xml.bind.JAXBException: Package java.util with JAXB class java.util.Date defined in a module java.base must be open to at least java.xml.bind module.

at java.xml.bind@9/javax.xml.bind.JAXB._marshal(Unknown Source)

at java.xml.bind@9/javax.xml.bind.JAXB.marshal(Unknown Source)

at Today.main(Today.java:4)

Caused by: javax.xml.bind.JAXBException: Package java.util with JAXB class java.util.Date defined in a module java.base must be open to at least java.xml.bind module.

at java.xml.bind@9/javax.xml.bind.ModuleUtil.delegateAddOpensToImplModule(Unknown Source)

at java.xml.bind@9/javax.xml.bind.ContextFinder.newInstance(Unknown Source)

at java.xml.bind@9/javax.xml.bind.ContextFinder.newInstance(Unknown Source)

Die zentrale Information ist „Package java.util with JAXB class java.util.Date defined in a module java.base must be open to at least java.xml.bind module“. Wir müssen etwas öffnen; dazu verwenden wir den Schalter –add-opens:

$ java –add-modules java.xml.bind –add-opens java.base/java.util=java.xml.bind Box

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

<date>2017-09-02T23:20:52.170+02:00</date>

Die Option öffnet für Reflection das Paket java.util aus dem Modul java.base für java.xml.bind. Neben –add-opens gibt es das ähnliche –add-exports, was alle öffentlichen Typen und Eigenschaften zur Übersetzungs-/Laufzeit öffnet; –add-opens geht für Reflection einen Schritt weiter.

Projektabhängigkeiten in Eclipse

Um Module praktisch umzusetzen wollen wir in Eclipse zwei neue Java-Projekte aufbauen: „com.tutego.greeter“ und „com.tutego.main“. Wir legen in das Projekt „com.tutego.greeter“ eine Klasse com.tutego.insel.greeter.Greeter an und in „com.tutego.main“ die Klasse com.tutego.insel.main.Main.

Jetzt ist eine wichtige Vorbereitung in Eclipse nötig: Wir müssen einstellen, dass „com.tutego.main“ das Java-Projekt „com.tutego.greeter“ benötigt. Dazu gehen wir auf das Projekt „com.tutego.main“ und rufen im Kontextmenü Project auf; alternativ im Menüpunkt Project > Properties oder über die Tastenkombination Alt + Return. Im Dialog navigiere links auf Java Build Path und aktiviere den Reiter Projects. Wähle Add… und im Dialog wähle aus der Liste com.tutego.greeter. Ok schließt den kleinen Dialog und unter Required projects in build path taucht eine Abhängigkeit auf.

Wir können jetzt zwei einfache Klassen implementieren. Zunächst für das Projekt „com.tutego.greeter“:

com/tutego/insel/greeter/Greeter.java

package com.tutego.insel.greeter;

public class Greeter {

private Greeter() { }

public static Greeter instance() {

return new Greeter();

}

public void greet( String name ) {

System.out.println( "Hey "+ name );

}

}

Und die Hauptklasse im Projekt „com.tutego.main“:

com/tutego/insel/main/Main

package com.tutego.insel.main;

import com.tutego.insel.greeter.Greeter;

public class Main {

public static void main( String[] args ) {

Greeter.instance().greet( "Chris" );

}

}

Da wir in Eclipse vorher die Abhängigkeit gesetzt haben, gibt es keinen Compilerfehler.

Benannte Module und module-info.java

Die Modulinformationen werden über eine Datei module-info.java (kurz Modulinfodatei) deklariert, Annotationen kommen nicht zum Einsatz. Diese zentrale Datei ist der Hauptunterschied zwischen einem Modul und einer einfachen JAR-Datei. In dem Moment, in dem die spezielle Klassendatei module-info.class im Modulpfad ist, beginnt die Laufzeitumgung das Projekt als Modul zu interpretieren.

Testen wir das, indem wir in unsere Projekte „com.tutego.greeter“ und „com.tutego.main“ eine Modulinfodatei anlegen. Das kann Eclipse über das Kontextmenü Configue > Create module-info.java für uns machen.

Für das erste Modul com.tutego.greeter entsteht:

module-info.java

/**

*

*/

/**

* @author Christian

*

*/

module com.tutego.greeter {

exports com.tutego.insel.greeter;

requires java.base;

}

Und für die zweite Modulinfodatei – Kommentare ausgeblendet:

module-info.java

module com.tutego.main {

exports com.tutego.insel.main;

requires com.tutego.greeter;

requires java.base;

}

Hinter dem Schlüsselwort module steht der Name des Moduls, den Eclipse automatisch so wählt wie das Eclipse-Projekt heißt.[1] Es folgt ein Block in geschweiften Klammern.

Zwei Schlüsselworte fallen ins Auge, die wir schon vorher bemerkt haben: exports und requires.

· Das Projekt/Modul com.tutego.greeter exportiert das Paket com.tutego.insel.greeter. Andere Pakete nicht. Es benötigt (requires) java.base, wobei das Modul Standard ist, und die Zeile gelöscht werden kann.

· Das Projekt/Modul com.tutego.main exportiert das Paket com.tutego.insel.main und es benötigt com.tutego.greeter – diese Information nimmt sich Eclipse selbstständig aus den Projektabhängigkeiten.

Info: Ein Modul required ein anderes Modul aber exports ein Paket.

Beginnen wir mit den Experimenten in den beiden module-info.java-Dateien:

Modul

Aktion

Ergebnis

com.tutego.greeter

com.tutego.main

// requires java.base;

Auskommentieren führt zu keiner Änderung, da java.base immer required wird

com.tutego.greeter

// exports com.tutego.insel.greeter;

Compilerfehler im main-Modul “The type com.tutego.insel.greeter.Greeter is not accessible”

com.tutego.greeter

exports com.tutego.insel.greeter to god;

Nur das Modul god bekommt Zugriff auf com.tutego.insel.greeter. Das main-Modul meldet “The type com.tutego.insel.greeter.Greeter is not accessible”

com.tutego.greeter

exports com.tutego.insel.closer;

Hinzufügen führt zum Compilerfehler “The package com.tutego.insel.closer does not exist or is empty”

com.tutego.main

// requires com.tutego.greeter;

Compilerfehler “The import com.tutego.insel.greeter cannot be resolved”

com.tutego.main

// exports com.tutego.insel.main;

Keins, denn c.t.i.m wird von keinem Modul required

Die Zeile mit exports com.tutego.insel.greeter to god zeigt einen qualifizierten Export.

Übersetzen und Packen von der Kommandozeile

Setzen wir in der Wurzelverzeichnis vom Modul com.tutego.greeter ein Batch-Skript compile.bat:

compile.bat

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

rmdir /s /q lib

mkdir lib

javac -d bin src\module-info.java src\com\tutego\insel\greeter\Greeter.java

jar –create –file=lib/com.tutego.greeter@1.0.jar –module-version=1.0 -C bin .

jar –describe-module –file=lib/com.tutego.greeter@1.0.jar

Folgende Schritte führt das Skript aus:

1. Setzen der PATH-Variable für die JDK-Tools

2. Löschen eines vielleicht schon angelegten lib-Ordners

3. Anlegen eines neuen lib-Orders für die JAR-Datei

4. Übersetzen der zwei Java-Dateien in den Zielordner bin

5. Anlegen einer JAR-Datei. –create (abkürzbar zu –c) instruiert das Werkzeug eine neue JAR-Datei anzulegen. –file (oder kurz –f) bestimmt den Zielnamen. –module-version unsere Versionsnummer und –C wechselt das Verzeichnis und beginnt ab dort die Dateien einzusammeln. Die Kommandozeilensyntax beschreibt Oracle auf der Webseite https://docs.oracle.com/javase/9/tools/jar.htm.

6. Die Option –describe-module (oder kurz –d) zeigt die Modulinformation und führt zu folgender (vereinfachten) Ausgabe: com.tutego.greeter@1.0 jar:file:///C:/…/com.tutego.greeter/lib/com.tutego.greeter@1.0.jar/!module-info.class exports com.tutego.insel.greeter requires java.base.

Für das zweite Projekt ist die compile.bat sehr ähnlich, dazu kommt ein Aufruf der JVM, um das Programm zu starten.

compile.bat

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

rmdir /s /q lib

mkdir lib

javac -d bin –module-path ..\com.tutego.greeter\lib src\module-info.java src\com\tutego\insel\main\Main.java

jar -c -f=lib/com.tutego.main@1.0.jar –main-class=com.tutego.insel.main.Main –module-version=1.0 -C bin .

java -p lib;..\com.tutego.greeter\lib -m com.tutego.main

Änderungen gegenüber dem ersten Skript sind:

1. Beim Compilieren müssen wir den Modulpfad mit –module-path (oder kürzer mit -p) angeben, weil ja das Modul com.tutego.greeter required ist.

2. Beim Anlegen der JAR-Datei geben wir über –main-class die Klasse mit der main(…)-Methode an.

3. Startet die JVM das Programm, lädt sie das Hauptmodul und alle abhängigen Module. Wir geben beide lib-Order mit den JAR-Dateien an und mit –m das sogenannte initiale Modul für die Hauptklasse.

Automatische Module

JAR-Dateien spielen seit 20 Jahren eine zentrale Rolle im Java-System; sie vom einen zum anderen Tag abzuschaffen würde große Probleme bereiten. Ein Blick auf https://mvnrepository.com/repos offenbart über 7,7 Millionen Artefakte; es gehen auch Dokumentationen und andere Dateien in die Statistik ein, doch es gibt eine Größenordnung, wie viele JAR-Dateien im Umlauf sind.

Damit JAR-Dateien unter Java 9 eingebracht werden können gibt es zwei Lösungen: das JAR in den Klassenpfad oder in den neuen Modulpfad zu setzen. Kommt ein JAR in den Modulpfad und hat es keine Modulinfodatei entsteht ein automatisches Modul. Bis auf eine kleine Einschränkung funktioniert das für die meisten existierenden Java-Bibliotheken.

Ein automatisches Modul hat gewisse Eigenschaften für den Modulnamen und Konsequenzen in den Abhängigkeiten:

· Ohne Modulinfo haben die automatischen Module keinen selbstgewählten Namen, sondern sie bekommen vom System einen Namen zugewiesen, der sich aus dem Dateinamen ergibt.[2] Vereinfacht gesagt: Angehängte Versionsnummern und die Dateiendung werden entfernt und alle nicht-alphanummerischen Zeichen durch Punkte ersetzt, jedoch nicht zwei Punkte hintereinander.[3] Die Version wird erkannt. Die Dokumentation gibt das Beispiel foo-bar-1.2.3-SNAPSHOT.jar an, was zum Modulnamen foo.bar und der Version 1.2.3-SNAPSHOT führt.

· Automatische Module exportieren immer alle ihre Pakete. Wenn es also eine Abhängigkeit zu diesem automatischen Modul gibt kann der Bezieher alle sichtbare Typen und Eigenschaften verwenden.

· Automatische Module können alle anderen Module lesen, auch die unbenannten.

Auf den ersten Blick scheint eine Migration in Richtung Java 9 einfach: Alle JARs auf den Modulpfad und nacheinander Modulinfodateien anlegen. Allerdings gibt es JAR-Dateien, die von der JVM als automatisches Modul abgelehnt werden, wenn sie nämlich Typen eines Paketes enthalten, und dieses Paket sich schon in einem anderen aufgenommen Modul befindet. Module dürfen keine „split packages“ enthalten, also das gleiche Paket noch einmal enthalten. Die Migration erfordert dann entweder a) das Zusammenlegen der Pakete zu einem Modul, b) die Verschiebung in unterschiedliche Pakete oder c) die Nutzung des Klassenpfades

Unbenanntes Modul

Eine Migration auf eine neue Java-Version sieht in der Regel so aus, dass zuerst die JVM gewechselt und geprüft wird, ob die vorhandene Software weiterhin funktioniert. Laufen die Testfälle durch und gibt es keine Auffälligkeiten im Testbetrieb kann der Produktivbetrieb unter der neuen Version erfolgen. Gibt es keine Probleme, können nach einiger Zeit die neuen Sprachmittel und Bibliotheken verwendet werden.

Übertragen wir das auf den Wechsel von Java 8 auf Java 9: Eine vorhandene Java-Software muss inklusive aller Einstellungen und Einträge im Klassenpfad weiterhin laufen. Das heißt, eine Java 9 Laufzeitumgebung kann den Klassenpfad nicht ignorieren. Da es intern nur einen Modulpfad gibt, müssen auch diese JAR-Dateien zu Modulen werden. Die Lösung ist das unbenannte Modul (eng. unnamed module): jedes JAR im Klassenpfad – dabei spielt es keine Rolle, ob es eine modul-info.class enthält – kommt in das unbenannte Modul. Davon gibt es nur eines, wir sprechen also im Singular, nicht Plural.

„Unbenannt“ sagt schon, dass das Modul keinen Namen hat, und folglich auch keine Abhängigkeit zu den JAR-Dateien im unbenannten Modul existieren kann; das ist der Unterschied zu einem automatischen Modul. Ein unbenanntes Modul hat die gleiche Eigenschaft wie ein automatisches Modul, dass es alle Pakete exportiert. Und weil es zur Migration gehört, hat ein unbenanntes Modul auch Zugriff auf alle anderen Module.

Lesbarkeit und Zugreifbarkeit

Die Laufzeitumgebung sortiert Module in einem Graphen ein. Die Abhängigkeit der Module führt dabei zu sogenannten Lesbarkeit (engl. readibility): Benötigt Modul A das Modul B, so ist liest A das Modul B und B wird von A gelesen. Für die Funktionsweise vom Modulsystem ist dies elementar, denn so werden zur Übersetzungszeit schon Fehler ausgeschlossen, wie Zkylen, oder gleiche Pakete in unterschiedlichen Modulen. Die Lesbarkeit ist zentral für eine zuverlässige Konfiguration, engl. reliable configuration.

Ein Schritt weiter geht der Begriff der Erreichbarkeit/Zugänglichkeit (engl. accessibility). Wenn ein Modul ein anderes Modul grundsätzlich lesen kann, bedeutet es noch nicht, dass es an alle Pakete und Typen kommt, denn nur die Typen sind sichtbar, die exportiert worden. Lesbare und erreichbare Typen nennen sich erreichbar.

Die nächste Frage ist, welcher Modultyp auf welchen anderen Modultyp Zugriff hat. Eine Tabelle fasst die Lesbarkeit am Besten zusammen:

Modultyp

Ursprung

Exportiert Pakete

Hat Zugriff auf

Plattformmodul

JDK

explizit

 

Benannte Module

Container mit Modulinfo im Modulpfad

explizit

Plattformmodule, andere benannte Module, automatische Module

Automatische Module

Container ohne Modulinfo im Modulpfad

alle

Plattformmodule, andere benannte Module, automatische Module, unbenanntes Modul

Unbenanntes Modul

Klassendateien und JARs im Klassenpfad

alle

Plattformmodule, benannte Module, automatische Module

Lesbarkeit der Module

Der Modulinfodatei kommt dabei die größte Bedeutung zu, denn sie macht aus einer JAR ein modular-JAR; fehlt die Modulinfomation bleibt es eine normale JAR, wie sie Java-Entwickler seit 20 Jahren kennen. Die JAR-Datei kann neu in den Modulpfad kommen oder in den bekannten Klassenpfad. Das ergibt vier Kombinationen:

 

Modulpfad

Klassenpfad

JAR mit Modulinfomation

wird benanntes Module

wird ubenanntes Modul

JAR ohne Modulinfomation

wird automatisches Modul

wird unbenanntes Modul

JARs im Pfad

JAR-Archive im Klassenpfad sind das bekannte Verhalten, weswegen auch ein Wechsel von Java 8 auf Java 9 möglich sein sollte.

Modul-Migration

Nehmen wir an, unsere monolithische Applikation hat keine Abhängigkeiten zu externen Bibliotheken und soll modularisiert werden. Dann besteht der erste Schritt darin, die gesamte Applikation in ein großes benanntes Modul zu setzen. Als nächstes müssen die einzelnen Bereiche identifiziert werden, damit nach und nach die Bausteine in einzelne Module wandern. Das ist nicht immer einfach, zumal zyklische Abhängigkeiten nicht unwahrscheinlich sind. Bei der Modularisierung des JDK hatten die Oracle-Entwickler viel Mühe.

Das Problem mit automatischen Modulen

Traditionell generieren Build-Werkzeuge wie Maven oder Gradle JAR-Dateien und ein Dateiname hat sich irgendwie ergeben. Werden jedoch diese JAR-Dateien zu automatischen Modulen spielt der Dateiname plötzlich eine große Rolle. Doch bewusst wurde der Dateiname vermutlich nie gewählt. Referenziert ein benanntes Modul ein automatisches Modul bringt das zwei Probleme mit sich: Ändert sich der Dateiname, lassen wir die Versionsnummer einmal außen vor, heißt auch das automatische Module anders, und die Abhängigkeit kann nicht mehr aufgelöst werden. Das zweite Problem ist größer. Viele Java-Bibliotheken haben noch keine Modulinformationen, und folglich werden Entwickler eine Abhängigkeit zu diesem automatischen Modul über den abgeleiteten Namen ausdrücken. Nehmen wir z. B. die beliebte Open-Source Bibliothek Google Guava. Die JAR-Datei hat den Dateinamen guava-23.0.jar – guava heißt folglich das automatische Modul. Ein benanntes Modul (nennen wir es M1) kann über required guava eine Abhängigkeit ausdrücken. Konvertiert Google die Bibliothek in ein echtes Java 9-Modul, dann wird sich der Name ändern – geplant ist com.google.guava. Und ändert sich der Name, führen alle Referenzierungen in Projekten zu einem Compilerfehler; ein Alias wäre eine tolle Idee, das gibt es jedoch nicht. Und das Problem besteht ja nicht nur im eigenen Code der Guava referenziert; Referenziert das eigene Modul M1 ein Modul M2, das wiederum Guava referenziert, so gibt es das gleiche Problem – wir sprechen von einer transitiven Abhängigkeit. Die Änderung vom Modulnamen von Guava wird zum Problem, denn wir müssen warten, bis M2 den Namen korrigiert, damit M1 wieder gültig ist.

Eine Lösung mildert das Problem ab: In der JAR-Manifest-Datei kann ein Eintrag Automatic-Module-Name gesetzt werden – das „überschreibt“ den automatischen Modulnamen.

Beispiel: Apache Commons setzt den Namen so:

Automatic-Module-Name: org.apache.commons.lang3

Benannte Module, die Abhängigkeiten auf automatische Module besitzen, sind also ein Problem. Es ist zu hoffen, dass die zentralen Java-Bibliotheken, auf die sich so viele Lösungen stützen, schnell Modulinformationen einführen. Das wäre eine Lösung von unten nach oben, englisch Bottom-Up. Das ist das einzige, was erfolgversprechend ist, aber wohl auch eine lange Zeit benötigen wird. Im Monat vom Java 9-Release hat noch keine wichtige Java-Bibliothek eine Modulinformation, Automatic-Module-Name kommt häufiger vor.


[1] Zur Benennung von Modulen gibt es Empfehlungen in dem englischsprachigen Beitrag http://mail.openjdk.java.net/pipermail/jpms-spec-experts/2017-May/000687.html

[2] Automatic-Module-Name in die META-INF-Datei zu setzen ist eine Alternative, dazu später mehr.

[3] Für Details siehe http://download.java.net/java/jigsaw/docs/api/java/lang/module/ModuleFinder.html#of-java.nio.file.Path…-

JMOD-Dateien und JAR-Dateien

Der Klassenlader bezieht .class-Dateien nicht nur aus Verzeichnissen, sondern in der Regel aus Containern. So müssen keine Verzeichnisse ausgetauscht werden, sondern nur einzelne Dateien. Als Container-Formate finden wir JMOD (neu in Java 9) und JAR. Wenn Java-Software ausgeliefert wird bieten sich JAR- oder JMOD-Dateien an, denn es ist einfacher und platzsparender, nur ein komprimiertes Archiv weiterzugehen als einen großen Dateibaum.

JAR-Dateien

Sammlungen von Java-Klassendateien und Ressourcen werden in der Regel in Java-Archiven, kurz JAR-Dateien, zusammengefasst. Diese Dateien sind im Grunde ganz normale ZIP-Archive mit einem besonderen Verzeichnis META-INF für Metadateien. Das JDK bringt im bin-Verzeichnis das Werkzeug jar zum Aufbau und Extrahieren von JAR-Dateien mit.

JAR-Dateien behandelt die Laufzeitumgebung wie Verzeichnisse von Klassendateien und Ressourcen. Zudem haben Java-Archive den Vorteil, dass sie signiert werden können und illegale Änderungen auffallen. JAR-Dateien können Modulinformationen beinhalten, dann heißen sie engl. modular JAR.

JMOD-Dateien

Das Format JMOD ist speziell für Module und neu in Java 9 – es organisiert Typen und Ressourcen. Zum Auslesen und Packen gibt es im bin-Verzeichnis des JDK das Werkzeug jmod.

Hinweis

Die JVM greift selbst nicht auf diese Module zurück. Achten wir auf die Ausgabe vom letzten Programmm, dann steht in der ersten Zeile:

[0.015s][info][class,load] opened: C:\Program Files\Java\jdk-9\lib\modules

Die Datei module ist ca. 170 MiB groß und in einem proprietären Dateiformat.

JAR vs. JMOD

Module können in JMOD- und JAR-Conatainer gepackt werden. Wenn ein JAR kein modular-JAR ist, also keine Modulinformationen enthält, so fehlen zentrale Informationen, wie Abhängigkeiten oder eine Version; ein JMOD ist immer ein benanntes Modul.

JMOD-Dateien sind nicht so flexibel wie JAR-Dateien, denn sie können nur zur Übersetzungszeit und zum Linken eines Runtime-Images – dafür gibt es das Kommandozeilenwerkzeug jlink – genutzt werden. JMOD-Dateien können nicht wie JAR-Dateien zur Laufzeit verwendet werden. Das Dateiformat ist proprietär und kann sich jederzeit ändern, es ist nichts Genaues spezifiziert.[1] Einziger Vorteil von JMOD: Native Bibliotheken lassen sich standardisiert einbinden.


[1] Die http://openjdk.java.net/jeps/261 macht die Aussage, dass es ein ZIP ist.

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.