Alle Beiträge von Christian Ullenboom

Über Christian Ullenboom

Ich bin Christian Ullenboom und Autor der Bücher ›Java ist auch eine Insel. Einführung, Ausbildung, Praxis‹ und ›Java SE 8 Standard-Bibliothek. Das Handbuch für Java-Entwickler‹. Seit 1997 berate ich Unternehmen im Einsatz von Java. Sun ernannte mich 2005 zum ›Java-Champion‹.

Eclipse Oxygen (4.7) ist fertig

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

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

 

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

Launching on Java 9

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

e.g.

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

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

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

Weiterhin:

Support for Building Java 9 Applications

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

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

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

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

Reaktive Programmierung und die Flow-API

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

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

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

@FunctionalInterface  

public static interface Flow.Publisher<T> { 

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

}  

 

public static interface Flow.Subscriber<T> { 

    public void    onSubscribe(Flow.Subscription subscription); 

    public void    onNext(T item) ; 

    public void    onError(Throwable throwable) ; 

    public void    onComplete() ; 

}  

 

public static interface Flow.Subscription { 

    public void    request(long n); 

    public void    cancel() ; 

}  

 

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

}

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

JSON-Verarbeitung mit der Java API for JSON Processing

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

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

<dependency>

  <groupId>javax.json</groupId>

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

  <version>1.1</version>

</dependency>

<dependency>

  <groupId>org.glassfish</groupId>

  <artifactId>javax.json</artifactId>

  <version>1.1</version>

</dependency>

Aufbauen von JSON-Objekten, Formatieren und Parsen

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

Point p = new Point( 10, 20 );

JsonObjectBuilder objBuilder = Json.createObjectBuilder()

                                   .add( "x", p.x )

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

JsonObject jsonObj = objBuilder.build();


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


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


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

config.put( JsonGenerator.PRETTY_PRINTING, true );

JsonWriterFactory writerFactory = Json.createWriterFactory( config );


StringWriter out = new StringWriter();

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

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


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

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

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

JSON-Streaming API

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

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



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

  while ( parser.hasNext() ) {

    switch ( parser.next() ) {

      case KEY_NAME:

      case VALUE_STRING:

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

        break;

      case VALUE_NUMBER:

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

        break;

      case VALUE_TRUE:

        System.out.println( true );

        break;

      case VALUE_FALSE:

        System.out.println( false );

        break;

      case VALUE_NULL:

      case START_ARRAY:

      case END_ARRAY:

      case START_OBJECT:

      case END_OBJECT:

        // Ignore

    }

  }

}

JAXB-Beans aus XML-Schema-Datei generieren

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

xjc aufrufen

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

$ xjc -help

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

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

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

Optionen:

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

  -extension         :  Lässt Herstellererweiterungen zu - Befolgt die

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

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

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

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

  -p <pkg>           :  Gibt das Zielpackage an

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  -verbose           :  Verwendet extra-verbose

  -quiet             :  Unterdrückt die Compilerausgabe

  -help              :  Zeigt diese Hilfemeldung an

  -version           :  Zeigt Versionsinformationen an

  -fullversion       :  Zeigt vollständige Versionsinformationen an







Erweiterungen:

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

  -Xlocator          :  enable source location support for generated code

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

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

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

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

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

Bibelvers über eine Bibel-API beziehen

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

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

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

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

<bible>

 <title>Jn3:16</title>

 <range>

  <request>Jn3:16</request>

  <result>John 3:16</result>

  <item>

   <bookname>John</bookname>

   <chapter>3</chapter>

   <verse>16</verse>

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

  </item>

 </range>

 <time>0.007</time>

</bible>

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

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

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

  <xsd:element name="bible">

    <xsd:complexType>

      <xsd:sequence>

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

        <xsd:element name="range">

          <xsd:complexType>

            <xsd:sequence>

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

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

              <xsd:element name="item">

                <xsd:complexType>

                  <xsd:sequence>

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

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

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

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

                  </xsd:sequence>

                </xsd:complexType>

              </xsd:element>

            </xsd:sequence>

          </xsd:complexType>

        </xsd:element>

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

      </xsd:sequence>

    </xsd:complexType>

  </xsd:element>

</xsd:schema>

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

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

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

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

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

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

Ein Schema wird geparst ...

Ein Schema wird kompiliert ...

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

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

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

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

package com.tutego.insel.xml.jaxb;




import javax.xml.bind.JAXB;

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




public class BibleOnline {

  public static void main( String[] args ) {

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

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

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

  }

}

 

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

Konflikte in der Schema-Datei *

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

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

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

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

JAXB-Plugins

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

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

Parsen und Formatieren von Datumszeitwerten der Java Date Time API

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

format(…)-Methode

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

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

Temporale Klassen nutzen den gleiche Parametertyp DateTimeFormatter

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

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

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

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

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

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

Beispiel

LocalDateTime now = LocalDateTime.now();

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

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

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

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

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

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

Die Ausgaben sehen so aus:

20170616

2017-06-16T20:39:11.2114379

16.06.17

16.06.2017, 20:39:11

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

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

Beispiel

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

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

parse(…)-Methode

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

Asnychrones Programmieren mit CompletableFuture (CompletionStage)

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

Ein Beispiel für einen trinkfesten mutigen Piraten:

package com.tutego.insel.concurrent;




import java.time.LocalTime;

import java.util.concurrent.CompletableFuture;

import java.util.concurrent.TimeUnit;

import java.util.logging.Logger;




class Pirate {




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




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

                                     .thenApply( Pirate::swears )

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

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

                                     .get();

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

  }




  static String newName() {

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

    return "Pirat Guybrush";

  }




  static String swears( String pirate ) {

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

    return pirate + " flucht";

  }




  static CompletableFuture<Integer> drinkRum() {

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

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

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

  }




  static String combinePiratAndDrinks( String pirat, int bottlesOfRum ) {

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

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

  }

}

Die Ausgabe ist:

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

INFORMATION: Thread[main,5,main]

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

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

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

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

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

INFORMATION: Thread[main,5,main]

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

INFORMATION: Thread[main,5,main]

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

INFORMATION: Thread[main,5,main]

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

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

HTTP Client API in Java 9

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

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

Die wesentlichen Typen sind:

  • HttpClient
  • HttpRequest, HttpResponse
  • WebSocket

Schauen wir uns ein Beispiel an:

try {

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

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

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

                                    httpRequest, HttpResponse.BodyHandler.asString() );

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

}

catch ( URISyntaxException | IOException | InterruptedException e ) {

  e.printStackTrace();

}

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

Die Methoden von HttpRequest sind allesamt abstrakt:

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

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

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

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

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

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

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

Insel-Update: Aufräumen mit finalize()

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

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

Sprachenvergleich

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

 

class java.lang.Object

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

Einmal Finalizer, vielleicht mehrmals die automatische Speicherbereinigung

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

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

super.finalize()

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

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

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

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

Hinweis

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

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

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

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

Gültige Alternativen

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

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

 

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

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

Versionskennungen aufbauen, parsen, vergleichen

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

Versionskennung

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

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

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

Versionsstring

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

Version-API

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

Beispiel

Gib alle Informationen über den Versionsstring aus:

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

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

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

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

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

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

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

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

Beispiel

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

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

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

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

Java 9 wieder verschoben um 2 Monate

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

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

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

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

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

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

Wird es bei Jigsaw (Module) in Java 9 bleiben?

RedHat hat mit  „The critical missing pieces and a path forward“ (http://mail.openjdk.java.net/pipermail/jpms-spec-observers/2017-May/000874.html) ausgedrückt, Jigsaw so nicht unterstützen zu können.

Tim Ellison von IBM legt noch einmal nach in http://mail.openjdk.java.net/pipermail/jpms-spec-observers/2017-May/000870.html und adressiert mit Links weitere Probleme.

Insgesamt sehe ich drei Ausgänge:

  1. Alles bleibt wie es ist und Oracle ignoriert die Probleme bzw. verschiebt Lösungen auf Java 10
  2. Das Release von Java 9 wird noch einmal nach hinten verschoben
  3. Jigsaw fliegt aus Java 9 raus

Was meint ihr?

JShell, die interaktive REPL-Shell in Java 9

Im JDK 9 ist ein neues Programm eingezogen: die JShell. Mit ihr lassen sich auf einfache Weise kleine Java-Programme und einzelne Anweisungen testen, sogenannte Snippets, ohne eine große IDE starten zu müssen. Die JShell ist eine Befehlszeile (Shell), die nach dem Read-Evaluate-Print-Loop-Prinzip arbeitet:

  • Read (Lesen): Eingabe des Programms von der Kommandozeile. Eine gute Shell bietet eine Historie der letzten Kommandos und Tastaturvervollständigung.
  • Eval (Ausführen): Compiliert das Snippet und führt es aus.
  • Print (Ausgaben): Die der Anweisungen und Programme werden in der Umgebung ausgegeben.
  • Loop (wiederholen): Es folgt ein Rücksprung auf den Zustand Lesen.

Das bekannteste Beispiel für eine REPL-Umgebung ist die Unix-Shell. Doch viele Skriptsprachen wie Lisp, Python, Ruby, Groovy und Clojure bieten solche REPL-Shells. Nun auch Java seit Version 9. Die Rückmeldung ist schnell, und gut zum Lernen und Ausprobieren von APIs.

Im bin-Verzeichnis vom JDK finden wir das Programm jshell. Rufen wir sie auf:

|  Welcome to JShell -- Version 9-ea

|  For an introduction type: /help intro




jshell>

Die JShell besitzt eingebaute Kommandos, die mit / beginnen, um zum Beispiel alle deklarierten Variablen ausgeben oder das Skript speichern. /help gibt eine Hilfe über alle Kommandos, /exit beendet JShell.

Nach dem Start wartet JShell auf die Snippets. Gültig sind:

  • Import-Deklarationen
  • Typ-Deklarationen
  • Methoden-Deklarationen
  • Anweisungen
  • Ausdrücke

Es sind also Teilmengen der Java-Sprache und keine eigene Sprache.

Anweisungen und einfache Navigation in der JShell

In der JShell lässt sich jeder Code schreiben, der im Rumpf einer Methode gültig ist. Ein Semikolon am Ende einer Anweisung ist nicht nötig:

jshell> System.out.println( "Hallo Welt" )

"Hallo Welt"

Compilerfehler zeigt die JShell sofort an:

jshell> System.out.pri()

|  Error:

|  cannot find symbol

|    symbol:   method pri()

|  System.out.pri()

|  ^------------^

Ausnahmen müssen nicht behandelt werden, es lassen sich alle Methoden ohne try-catch aufrufen; falls es zu Ausnahmen kommt werden diese direkt gemeldet:

jshell> Files.exists( Paths.get("c:/") )

$2 ==> true




jshell> Files.exists( Paths.get("lala:/") )

|  java.nio.file.InvalidPathException thrown: Illegal char <:> at index 4: lala:/

|        at WindowsPathParser.normalize (WindowsPathParser.java:182)

…

|        at (#3:1)

Die letzte Zeile zeigt die Zeilennummer im Skript an. Eine Liste der bisher eingegebenen Zeilen listet /list auf, und das inklusive Zeilennummern. Diese sind nützlich, wenn es zu Ausnahmen wie oben kommt.

jshell> /list




   1 : System.out.println( "Hallo Welt" );

   2 : Files.exists(Paths.get("c:/"))

   3 : Files.exists(Paths.get("lala:/"))

Die JShell pflegt eine Historie der letzten Kommandos, die sich mit den Cursor-Tasten abrufen lässt. Es funktioniert auch die Vervollständigung mit der Tabulator-Taste wie in einer IDE, wobei die Groß-Kleinschreibung relevant ist:

jshell> Sys

System        SystemColor   SystemTray




jshell> System.out.println(java.time.LocalDateTime.n

jshell> System.out.println(java.time.LocalDateTime.now(

now(




jshell> System.out.println(java.time.LocalDateTime.now())

2017-03-23T11:50:43.859385900

Mit dem Cursor lässt sich in die Zeile vorher gehen und die Zeile nacheditieren.

Variablendeklarationen

Variablen lassen sich deklarieren und später jederzeit verwenden:

jshell> String name = "Christian"

name ==> "Christian"

Die JShell gibt die Variable mit der Belegung zur Kontrolle aus.

Variablen lassen sich mit einem ganz neuen Typ redefinieren:

jshell> StringBuilder name = new StringBuilder( "Christian" )

name ==> Christian

Es lassen sich auch ohne Zuweisung Ausdrücke in die JShell setzen. Das Ergebnis des Ausdrucks wird einer temporären Variablen zugewiesen, die standardmäßig mit einem Dollar beginnt und der einer Zahl folgt, etwa $1. Auf diese Variable lässt sich später zugreifen:

jshell> BigInteger.TEN.pow(10)

$1 ==> 10000000000




jshell> $1

$1 ==> 10000000000




jshell> $1.bitLength()

$2 ==> 34




jshell> System.out.println(2*$2)

68

Welche Variablen in welcher Reihenfolge in der Sitzung deklariert wurden zeigt das Kommando /vars auf:

jshell> /vars

|    StringBuilder name = Christian

|    BigInteger $1 = 10000000000

|    int $2 = 34

Unvollständige Eingabe

Wenn die JShell auf einen nicht kompletten Code trifft, symbolisiert die Ausgabe …> die Notwendigkeit einer weitere Eingabe:

jshell> System.out.println(

   ...> "Hallo"

   ...> +

   ...> " Welt"

   ...> )

Hallo Welt

Import-Deklarationen

Standardmäßig sind für den Java-Compiler alle Typen vom Paket java.lang direkt importiert. Die JShell erweitert das um eine ganze Reihe weiterer Typen. Wir können sie mit dem Kommando /imports erfragen:

jshell> /imports

|    import java.io.*

|    import java.math.*

|    import java.net.*

|    import java.nio.file.*

|    import java.util.*

|    import java.util.concurrent.*

|    import java.util.function.*

|    import java.util.prefs.*

|    import java.util.regex.*

|    import java.util.stream.*




jshell> import java.awt.*




jshell> /imports

|    import java.io.*

|    import java.math.*

|    import java.net.*

|    import java.nio.file.*

|    import java.util.*

|    import java.util.concurrent.*

|    import java.util.function.*

|    import java.util.prefs.*

|    import java.util.regex.*

|    import java.util.stream.*

|    import java.awt.*

Methoden- und Typ-Deklarationen

Methoden und Klassen lassen sich deklarieren und auch wieder überschreiben, wenn eine neue Version eine alte ersetzen soll. JShell schreibt dann „modified“ bzw. „replaced“.

jshell> String greet(String name) { return "BÖLK " + name; }

|  created method greet(String)




jshell> String greet(String name) { return "Mit vorzüglicher Hochachtung " + name; }

|  modified method greet(String)




jshell> class MyFrame extends java.awt.Frame {}

|  created class MyFrame




jshell> class MyFrame extends java.awt.Frame { MyFrame() { setTitle("FENSTER"); } }

|  replaced class MyFrame




jshell> new MyFrame().show()

Welche Methoden und neue Typen in der Sitzung deklariert sind listet /methods und /types auf:

jshell> /methods

|    String greet(String)




jshell> /types

|    class OkButton

|    class MyFrame

Exceptions müssen wie üblich behandelt werden, eine Sonderbehandlung, wie bei der direkten, in die JShell eingegeben Anweisungen, gibt es nicht.

Forward-Reference

Greift eine Methoden- oder Klassendeklaration auf Typen und Methoden zurück, die in dem Kontext noch nicht vorhanden sind, ist das in Ordnung; allerdings müssen alle Typen und Methoden spätestens dann bekannt sein, wenn der Code ausgeführt werden soll.

jshell> double cubic(double v) { return sqr(v) * v; }

|  created method cubic(double), however, it cannot be invoked until method sqr(double) is declared




jshell> cubic(100)

|  attempted to call method cubic(double) which cannot be invoked until method sqr(double) is declared




jshell> double sqr(double v) { return v*v; }

|  created method sqr(double)




jshell> cubic(100)

$14 ==> 1000000.0

Laden, speichern und ausführen von Skripten

Snippets können in der JShell mit /save Dateiname gespeichert, mit /open Dateiname geöffnet und mit /edit in einem Standard-Editor bearbeitet werden.

Auf der Kommandozeile werden JShell-Skripte auf vorhandenen Skripten einfach ausgeführt mit:

$ jshell datei

JShell API

Anders als die Benutzung von JavaScript aus Java heraus integriert sich die JShell nicht als Skript-Sprache. Stattdessen gibt es eine eigene API, in der die Klasse JShell im Mittelpunkt steht, wobei sich die Möglichkeiten der JShell-Kommandozeile eins zu eins in der API – dokumentiert unter http://download.java.net/java/jdk9/docs/jdk/api/jshell/overview-summary.html – wiederfinden lassen.

Ein einfaches Beispiel:

try ( JShell shell = JShell.create() ) {

  // Semikolon wichtig!

  String program = "java.math.BigInteger.TEN.pow( 10 );";

  List<SnippetEvent> events = shell.eval( program );

  for ( SnippetEvent snippetEvent : events ) {

    System.out.println( snippetEvent.status() );

    System.out.println( snippetEvent.value() );

    System.out.println( snippetEvent.snippet().source() );

    System.out.println( snippetEvent.snippet().kind() );

    if ( snippetEvent.snippet() instanceof VarSnippet ) {

      VarSnippet varSnippet = (VarSnippet) snippetEvent.snippet();

      System.out.println( varSnippet.typeName() );

    }

  }

}

Die Ausgabe ist:

VALID

10000000000

java.math.BigInteger.TEN.pow( 10 );

VAR

java.math.BigInteger

Ein paar Dinge sind an der API bemerkenswert, und zwar die Typen und Ergebnisse: sie sind Strings. varSnippet.typeName() ist ein String und snippetEvent.value() ebenso. Es ist nicht möglich, eine echte Objektrepräsentation zu bekommen, was die Nutzung als eingebettete Skriptsprache einschränkt.

Zum Weiterlesen

Weitere Informationen lassen sich aus dem JEP 222: jshell: The Java Shell (Read-Eval-Print Loop)[1] entnehmen und von den Quellen, die bei Java 9 dabei sind. Fragen über das Produkt lassen sich in der Mailingliste http://mail.openjdk.java.net/mailman/listinfo/kulla-dev stellen.

[1]            http://openjdk.java.net/jeps/222

Inselraus: JDBC Treibertypen

Oracle definiert vier Treiberkategorien, die wir im Folgenden beschreiben. Sie unterscheiden sich im Wesentlichen darin, ob sie über einen nativen Anteil verfügen oder nicht.

Typ 1: JDBC-ODBC-Brücke (JDBC-ODBC Bridge driver)

ODBC (Open Database Connectivity Standard) ist ein Standard von Microsoft, der den Zugriff auf Datenbanken über eine genormte Schnittstelle möglich macht. ODBC ist insbesondere in der Windows-Welt weit verbreitet, und für jede ernst zu nehmende Datenbank gibt es einen Treiber.

Da es am Anfang der JDBC-Entwicklung keine Treiber gab, haben sich JavaSoft und Intersolv (seit 2000 Merant) etwas ausgedacht: eine JDBC-ODBC-Brücke, die die Aufrufe von JDBC in ODBC-Aufrufe der Client-Seite umwandelt. Da die Performance oft nicht optimal und die Brücke nicht auf jeder Plattform verfügbar ist, stellt diese JDBC-Anbindung häufig eine Notlösung dar. Und weil ODBC eine systembezogene Lösung ist, hat der Typ-1-Treiber native Methoden, was die Portierung und seinen Einsatz – etwa über das Netz – erschwert. Die JDBC-ODBC-Brücke implementiert seit Version 1.4 den JDBC 2-Standard. In Java 8 wurde die Brücke entfernt, ist also kein Teil mehr vom JDK. Es gibt existierende Lösungen auf dem Markt, allerdings gibt es keine wirkliche Notwendigkeit mehr dafür, da es für jede bedeutende Datenbank einen direkten JDBC-Treiber gibt.

Typ 2: Native plattformeigene JDBC-Treiber (Native-API Java driver)

Diese Treiber übersetzen die JDBC-Aufrufe direkt in Aufrufe der Datenbank-API. Dazu enthält der Treiber Programmcode, der native Methoden aufruft.

Treiber vom Typ 1 oder 2 sind nicht portabel auf jeder Plattform, da sie zum einen für die JDBC-ODBC-Brücke auf die Plattform-Bibliothek für ODBC zurückgreifen müssen und zum anderen auf plattformspezifische Zugriffsmöglichkeiten für die Datenbank angewiesen sind. Den Treiber auf einen anderen Computer zu übertragen funktioniert in der Regel nicht, da sie eng mit der Datenbank verbunden sind. Läuft auf einem Server etwa eine alte dBASE-Datenbank und ein nativer Typ-2-Treiber greift direkt auf die lokale Datenbank zu, dann kann dieser JDBC-Treiber nicht einfach auf einen Tablet-PC mit ARM-Prozessor kopiert werden – zum einen würde er dort wegen der anderen Hardware-Architektur nicht laufen, und zum anderen würde der Treiber dann wohl kaum über das Netzwerk auf die Datenbank zugreifen können.

Typ 3: Universeller JDBC-Treiber (Native-protocol all Java driver)

Der universelle JDBC-Treiber ist ein in Java programmierter Treiber, der beim Datenbankzugriff auf den Client geladen wird. Der Treiber kommuniziert mit der Datenbank nicht direkt, sondern über eine Softwareschicht, die zwischen der Anwendung und der Datenbank sitzt: der Middleware. Damit erfüllen Typ-3-Treiber eine Vermittlerrolle, denn erst die Middleware leitet die Anweisungen an die Datenbank weiter. Für Applets und Internetdienste hat ein Typ-3-Treiber den großen Vorteil, dass seine Klassendateien oft kleiner als Typ-4-Treiber sind, da ein komprimiertes Protokoll eingesetzt werden kann. Über das spezielle Protokoll zur Middleware ist auch eine Verschlüsselung der Verbindung möglich. Kaum eine Datenbank unterstützt verschlüsselte Datenbankverbindungen. Da zudem das Middleware-Protokoll unabhängig von der Datenbank ist, müssen auf der Client-Seite für einen Datenbankzugriff auf mehrere Datenbanken auch nicht mehr alle Treiber installiert werden, sondern im günstigsten Fall nur noch ein Typ-3-Treiber von einem Anbieter. Die Ladezeiten sind damit deutlich geringer.

Typ 4: Direkte Netzwerktreiber (Net-protocol all Java driver)

Diese Treiber sind vollständig in Java programmiert und kommunizieren direkt mit dem Datenbankserver. Sie sprechen mithilfe des datenbankspezifischen Protokolls direkt mit der Datenbank über einen offenen IP-Port. Dies ist in einer Direktverbindung die performanteste Lösung. Sie ist jedoch nicht immer realisierbar, etwa bei Datenbanken wie MS Access, dBase oder Paradox, die kein Netzwerkprotokoll definieren.

Schneller aufrufen mit MethodType und MethodHandle

Um dynamische Programmiersprachen wie JavaScript performant auf die JVM zu bringen wurde in Java 7 der neue Bytecode invokedynamic eingeführt und das Paket lang.invoke. Java8 greift zur Umsetzung der Lambda-Ausdrücke massiv auf invokedynamic zurück.

Aufgabe des Pakets ist es, dynamisch Methoden schnell aufzurufen, und zwar deutlich schneller als Reflection das kann, weil in der JVM der Methodenaufruf so optimiert wird wie ein ganz normaler Methodenaufruf auf ein Zielobjekt. Dazu müssen aber die Typen exakt vorliegen, sodass es schärfere Anforderungen gibt als bei Reflection, dort ist z. B. der Rückgabetyp beim Aufruf irrelevant.

Um einen dynamischen Methodenaufruf zu starten ist zunächst eine exakte Beschreibung der Rückgabe- und Parametertypen nötig – das übernimmt ein MethodType-Objekt. Ist das aufgebaut, wird ein MethodHandle erfragt, ein getypter, direkt ausführbarer Verweis auf die repräsentierte Methode. Die MethodHandle-Methode invoke(…) führt dann den Aufruf mit gegebenen Argumente auf.

Dazu ein Beispiel. Wir möchten auf einem Rectangle-Objekt die Methode union(Rectangle) aufrufen, um als Ergebnis ein neues Rectangle zu bekommen, was die beiden Rechtecke vereinigt.

Object rect1 = new Rectangle( 10, 10, 10, 10 );

String methodName = "union"; 

Class<?> resultType = Rectangle.class;

Object rect2 = new Rectangle( 20, 20, 100, 100 );

Class<? > parameterType = rect2.getClass();

rect1 ist das eigentliche Objekt auf dem die methodName aufgerufen werden soll. resultType ist der Ergebnistyp den wir von der Methode erwarten, als Class-Objekt. rect2 ist das Argument für union(…). Der parameterType für die union(…)-Methode ergibt sich aus dem Class-Objekt vom rect2.

MethodType mt = MethodType.methodType( resultType, parameterType );

MethodHandle methodHandle = MethodHandles.lookup().findVirtual(

                              rect1.getClass(), methodName, mt );

System.out.println( methodHandle.invoke( rect1, rect2 ) );

Als erstes erfragen wir MethodType und geben Ergebnis- und Parametertyp an; noch nicht den Methodennamen, hier geht es nur um die Typen. Der MethodHandle verheiratet den Methodennamen und die Typangaben mit einer Klasse, die so eine Methode anbietet. invoke(…) führt letztendlich den Aufruf aus; das erste Argument ist das „this“-Objekt, also das Objekt auf dem die Methode aufgerufen werden soll, als nächstes folgenden die Argumente von union(…), also das zweite Rechteck. Als Ergebnis bekommen wir das Rectangle-Objekt, was genau der Vereinigung entspricht.

Java DB nicht mehr in Java 9

Gerade habe ich angefangen mein JDBC-Kapitel zu aktualisieren, weg von HSQLDB hin zur mitgelieferten  Java DB. Ich habe die Java DB-Datenbank gestartet, den Text und Beispiele umgeschrieben, usw. Irgendwie habe ich das db-Verzeichnis von Java 8 genutzt, ohne das mir das aufgefallen wäre. Das Kapitel ist fertig gewesen, da wollte ich schauen, ob Java 9 die aktuelle Version von Apache DB nutzt und was ist? Arrrg. In Java 9 ist die Java DB rausgeflogen. Verdammt. Alle Änderungen wieder rückgängig machen, es bleibt vorerst bei HSQLDB.

Insel Java 9 Update; Statusbericht 1

Wie man an den Beiträgen hier im Blog erkennen kann, sind viele Absätze aktualisiert worden mit den Methoden aus Java 9. Der Kleinkram ist komplett beschrieben, was jetzt noch fehlt:

  • Modulsystem natürlich, größter neu zu schreibender Text
  • Reactive Programming mit  java.util.concurrent.Flow.*, allerdings überlege ich noch, wie tief ich das beschreiben möchte

Im Allgemeinen war das Update auf Java 9 in Ordnung, newInstance() auf Class habe ich öfters gebraucht, das ist jetzt deprecated. Mein Bsp. mit der Javadoc API funktioniert nicht mehr, das Modulsystem macht mir einen Strich durch das Programm.

Mit der Gewichtung Swing/Java FX bin ich nicht zufrieden. Java FX ist weiterhin kein Teil der offiziellen Java SE, sondern nur Teil vom Oracle/Open JDK. Außerdem ist die Zukunft auch hier unsicher, jetzt, wo schon so viele Entwickler vom Projekt. abgezogen wurden. Der Trend geht ganz klar Richtung Web, sodass ich eigentlich beide Kapitel massiv kürzen sollte bis nur noch eine Einleitung stehen bleibt und noch mal ein Web-Kapitel ergänzen sollte. Was ist eure Meinung?