Alle Neuerungen hier: https://www.eclipse.org/eclipse/news/4.7.1a/
Java 9 ist raus
Und zum Download bereit. Java 8 ist damit von der Download-Seite verdrängt.
Chief Architect der Java Platform Group bei Oracle Mark Reinhold gibt es paar Details unter https://mreinhold.org/blog/jigsaw-complete, warum Jigsaw so wichtig ist.
Und jetzt freuen wir uns auf Java 10 ….
JUnit 5 ist raus
Alle Details auf der schön gemachten Webseite http://junit.org/junit5/.
Überraschung: Java EE bei der Eclipse Foundation
Details bei Oracle im Blog: https://blogs.oracle.com/theaquarium/opening-up-ee-update.
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
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.
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.
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)
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 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
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 pack1
references 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 X
in directiveuses X;
in file first -> module-info.java is shown, similar to other X
references 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 X
to 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); } }
@SafeVarargs
inserted 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 private
as 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:
- JSON-Übersetzungdes Java-Objekts über die JSON-Bibliothek („POJO based JSON binding support“). Es lassen sich so ziemlich alle Java-Objekte abbilden.
- 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.
- 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:
- Ä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/.
- 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.
- 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).
Spring Tool Suite 3.9.0 erschienen
Alle Neuigkeiten unter http://docs.spring.io/sts/nan/v390/NewAndNoteworthy.html
Link: Was ist neu in Spring 5.0
Link: Java Module Platform System (JSR 376) Passes the Public Review Reconsideration Ballot
https://www.infoq.com/news/2017/07/jsr-376-approved
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.
Asynchrones Programmieren mit CompletableFuture (CompletionStage)
So schöne an Future-Objekten ist die Abarbeitung im Hintergrund und die Möglichkeit, später abzufragen, ob das Ergebnis schon da ist. Allerdings fehlt der Future-Schnittstelle eine Methode, automatisch nach der Fertigstellung einen Folgeauftrag abzuarbeiten. Dafür bietet die Java-Bibliothek eine spezielle Unterklasse CompletableFuture. Die Klasse implementiert die Schnittstelle CompletionStage, die vermutlich die größte Anzahl Operationen in der gesamten Java SE hat. Der Typname drückt aus, das es um die Fertigstellung (engl. completion) von Abschnitten (engl. stage) geht.
Ein Beispiel für einen trinkfesten mutigen Piraten:
package com.tutego.insel.concurrent; import java.time.LocalTime; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; class Pirate { public static void main( String[] arg ) throws Throwable { String result = CompletableFuture.supplyAsync( Pirate::newName ) .thenApply( Pirate::swears ) .thenCombine( drinkRum(), Pirate::combinePiratAndDrinks ) .thenCombine( drinkRum(), Pirate::combinePiratAndDrinks ) .get(); System.out.println( result ); // Pirat Guybrush flucht und trinkt dann 10 Flaschen Rum und trinkt dann 11 Flaschen Rum } static String newName() { Logger.getGlobal().info( "" + Thread.currentThread() ); return "Pirat Guybrush"; } static String swears( String pirate ) { Logger.getGlobal().info( "" + Thread.currentThread() ); return pirate + " flucht"; } static CompletableFuture<Integer> drinkRum() { Logger.getGlobal().info( "" + Thread.currentThread() ); try { TimeUnit.SECONDS.sleep( 1 ); } catch ( Exception e ) { } return CompletableFuture.supplyAsync( () -> LocalTime.now().getSecond() ); } static String combinePiratAndDrinks( String pirat, int bottlesOfRum ) { Logger.getGlobal().info( "" + Thread.currentThread() ); return pirat + " und trinkt dann " + bottlesOfRum + " Flaschen Rum"; } }
Die Ausgabe ist:
Juni 15, 2017 10:41:56 NACHM. com.tutego.insel.thread.concurrent.Pirate drinkRum INFORMATION: Thread[main,5,main] Juni 15, 2017 10:41:56 NACHM. com.tutego.insel.thread.concurrent.Pirate newName INFORMATION: Thread[ForkJoinPool.commonPool-worker-1,5,main] Juni 15, 2017 10:41:56 NACHM. com.tutego.insel.thread.concurrent.Pirate swears INFORMATION: Thread[ForkJoinPool.commonPool-worker-1,5,main] Juni 15, 2017 10:41:57 NACHM. com.tutego.insel.thread.concurrent.Pirate combinePiratAndDrinks INFORMATION: Thread[main,5,main] Juni 15, 2017 10:41:57 NACHM. com.tutego.insel.thread.concurrent.Pirate drinkRum INFORMATION: Thread[main,5,main] Juni 15, 2017 10:41:58 NACHM. com.tutego.insel.thread.concurrent.Pirate combinePiratAndDrinks INFORMATION: Thread[main,5,main] Pirat Guybrush flucht und trinkt dann 57 Flaschen Rum und trinkt dann 58 Flaschen Rum
Zum Programm: Zunächst muss die Kette von Abschnitten aufgebaut werden. Das kann entweder mit dem Standardkonstruktor geschehen, oder mit statischen Methoden. In unserem Fall nutzen wir supplyAsync(Supplier<U> supplier). Die Methode nimmt sich einen freien Thread aus dem ForkJoinPool.commonPool() und lässt den Thread den supplier abarbeiten. Das Ergebnis ist über die Rückgabe, einem CompletableFuture, abrufbar. Als nächsten wenden wir thenApply(Function<? super T,? extends U> fn) an, die vergleichbar ist mit einer map(…)-Operation eines Streams. Interessant wird es bei thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn); sie verbindet das Ergebnis der eigenen CompletionStage über eine Funktion mit einer anderen CompletionStage, die wir in unserem Fall auch wieder mit supplyAsync(…) aufbauen. So kombinieren wir zwei unabhängige CompletionStage miteinander und synchronisieren das Ergebnis. Wir können das gut an der Ausgabe auslesen, dass drinkRum ganz am Anfang schon ausgeführt wird, und zwar vom Thread[main,5,main], nicht vom ForkJoinPool, weil es unabhängig von den anderen läuft.
Java 9 Early-Access Builds
Sind verfügbar unter http://jdk.java.net/9/.