Java 9 und Jigsaw

Neue Tools in Java 9

Java 9 Modul-Konzept

Die wichtigste Neuerung von Java 9 ist zweifellos das neue Modul-Konzept – auch als “jigsaw” bezeichnet (JEPs 200, 201, 220, 260, 261, 281).

Das Modulkonzept verfolgt die Ziele “reliable configuration” und “strong encapsultion”. Was ist damit gemeint?

Im “alten” Java werden Klassen Pakete zugeordnet. Nur die als “public” gekennzeichneten Klassen eines Pakets können von Klassen anderer Pakete genutzt werden. Nicht-öffentliche Klassen können nur in anderen Klassen desselben Pakets verwendet werden.

Das Paket war bislang die höchste Organisations-Einheit, die Java kannte. Natürlich konnten class-Dateien zu jar-Dateien zusammengefasst werden – eine jar-Datei war bislang aber eben nichts anderes als ein gezipptes Verzeichnis mit class-Dateien. Eine jar-Datei hatte also kein dem Compiler oder der VM bekannte Java-Beschreibung. Das “Interface” einer jar war identisch mit ihrem Inhalt.

Daraus ergaben sich Probleme:

  • Angenommen, eine Anwendung enthält Klassen der Pakete jj.mod.pub und jj.mod.pri. In jj.mod.pub existiert die Klasse Foo, in jj.mod.pri existiert die Klasse Bar – eine Helper-Klasse, die von Foo genutzt wird. Bar muss nun public sein – ansonsten könnte sie von Foo nicht genutzt werden. Damit ist Bar natürlich auch für alle anderen Klassen nutzbar – obwohl sie nur als Helper-Klasse für Foo gedacht war. Wobei es völlig unerheblich ist, wie die class-Dateien zu jars zusammengefasst werden.
  • Klassen, die ein und demselben Paket zugeordnet waren, konnten auf verschiedene jar-Dateien aufgeteilt werden. Das trug nicht unbedingt zur Übersichtlichkeit bei.
  • Die Klassen, die zur Compilationszeit und zur Laufzeit herangezogen wurden, wurden über den CLASSPATH ermittelt. Enthielt der CLASSPATH zwei gleichnamige Klassen, so wurde die erste dieser Klassen herangezogen (die Änderung der Reihenfolge der Elemente des CLASSPATH konnte also eine sehr überraschende Wirkung haben).
  • Einer jar-Datei konnte man nicht ansehen, von welchen anderen jars sie abhängig war. Im Grunde war es immer Glücksache, alle benötigten jar-Dateien aufgefunden und bereitgestellt zu haben. Dieses Problem kann zwar durch die Benutzung von Build-Werkzeugen wie Apache Maven oder auch dem OSGi-Framework gelöst werden, aber eben nicht im Rahmen des Java-Standards.

Solche und ähnliche Probleme können vermieden werden, wenn das neue Modul-Konzept verwendet wird.

Ein Modul ist jar-Datei, die in ihrem Wurzelverzeichnis eine module-info.class enthält. Diese Datei wird vom Compiler aufgrund einer module-Info.java-Datei. Diese Datei enthält die Selbstbeschreibung des Moduls. Zu dieser Beschreibung gehören u.a. zwei wichtige Elemente: welche Pakete des Moduls sind von anderen Modulen nutzbar? – und von welchen anderen Modulen ist das Modul abhängig?

Wir greifen auf das obige Szenario zurück. Wir bauen ein Modul mit den Klassen Foo und Bar:

package jj.mod.pub;
 import jj.mod.pri;
 public class Foo {
 new Bar();
 }

package jj.mod.pri;
 public class Bar {
 }

Diese beiden Klasse sollen ein Modul namens jj.mod bilden. Wir schreiben folgende module-info.java:

module jj.mod {
 exports jj.mod.pub;
 }

Das Modul hat einen eigenständigen Java-Namen:  jj.mod. Solche Modul-Namen werden nach demselben Schema gebildet wie Paket-Namen. Der Modul-Name, die Namen der Pakete, die das Modul zusammenfasst, sind technisch völlig unabhängig voneinander – dennoch sollte natürlich ein gewisser Zusammenhang erkennbar sein.

Eine Modul jj.appl, welches dieses Modul nutzt, wird folgende Beschreibungsdatei enthalten:

module jj.appl {
 requires jj.mod;
 }

Die Klassen des jj.appl-Module können nun die Klassen aller Pakete des Moduls jj.mod nutzen, welche von diesem Modul exportiert werden – hier also nur die Klasse Foo:

package jj.appl;

public class Applicaiton {
 new jj.mod.pub.Foo();
 // new jj.mod.pri.Bar(); // illegal
 }

Module können zudem transitiv exportiert werden; und sie können ihre Pakete nur einer begrenzten Menge anderer Module zur Nutzung zur Verfügung stellen.

Mit diesem neuen Modul-Konzept wird der Modul-Pfad eingeführt, der den CLASSPATH ersetzen kann. Wird statt des CLASSPATH der Modul-Pfad verwendet, wird zudem verhindert, dass Pakete auf mehrere Module verteilt werden können.

Natürlich muss gewährleistet sein, dass auch “alte” jar-Dateien weiterhin genutzt werden können: sofern eine jar-Datei keine module-info.class enthält, exportiert sie implizit all ihre Pakete – und kann auf alle anderen jar-Dateien (auch auf modulare) zugreifen.

Und umgekehrt kann auch eine alte VM die neuen modularen jars nutzen – alte VMs ignorieren eine Datei namens module-info.class (“illegaler” Klassenname wegen des Bindestrichs).

Auch die rt.jar ist übrigens in mehrere Module zerlegt worden (eine Anwendung muss nur diejenigen Java-SE-Module an Bord haben, die sie tatsächlich benötigt).

Insbesondere bei der Erstellung großer Programmsysteme und bei der Erstellung von Bibliotheken ist das neue Modul-Konzept natürlich von grundlegender Bedeutung.

Publisher-Subscriber

Java 9 führt ein Publisher-Subscriber-Framework ein, welches der Reactive Streams Specification entspricht (java.utilconcurrent.Flow). Reactive Streams sind “asynchronous streams of data with non-blocking back pressure”. Ein reactive stream lässt sich grob als eine Kombination des Iterator- und des Observer-Patterns. Ein Iterator folgt dem Pull-Modell; ein Oberser folgt dem Push-Modell. Beim Flow-API fordert zunächst der Subscriber eine bestimmte Anzahl von zu verarbeitenden Objekten an (pull); dann stellt der Publisher diese Objekte dem Supplier in der von ihm gewünschten Anzahl zu (push). Das von Java 9 eingeführte Framework spezifiziert allerdings ausschließlich Interfaces – Implementierungen dieser Interfaces werden bereitgestellt z.B. von RxJava. (JEP 266)

jshell

Java 9 enthält nun eine jshell, ein interaktives Tool zur Auswertung von Deklarationen, Expressions und Anweisungen. Das Tool implementiert das REPL-Muster (Read-Eval-Print-Loop). Zwei kleine Beispiele:

jshell> Math.sqrt(2)

$1 ==> 1.4142135623730951

 

jshell> {     

   …> int a = 25;     

   …> int b = 15;     

   …> while (a != b) {     

   …> if (a > b) a -= b; else b -= a;     

   …> }     

   …> System.out.println(a);     

   …> }

5

Man kann nun also Java-Fragmente ausprobieren, ohne zunächst den Code komplett übersetzen und zu seiner Ausführung eine VM starten zu müssen (JEP 222).

Collection-Factory-Methods

Java 9 führt einige neue Factory-Methoden ein, mittels derer auf bequeme Weise immutable Collections erzeugt werden können (List.of, Set.of, Map.of, Map.ofEntries).

Erweiterungen der Klasse Process

Die Klasse Process erlaubt nun eine wesentlich erweiterte Kontrolle und Monitoring nativer Prozesse (diese Möglichkeiten waren bislang recht begrenzt). Neu sind in diesem Kontext u.a. die Interfaces ProcessHandle und ProcessHandle.Info.

Stream-API

Das Stream-API wurde erweitert. Neu sind hier insbesondere die Methoden dropWhile und takeWhile.

StackWalker

Informationen über den aktuellen Stack mussten bislang mit der Throwable-Methode getStackTrace abgefragt werden. Diese Methode liefert den kompletten Stack zurück. Mittels der in Java-9 eingeführten Klasse StackWalker können solche Informationen nun lazy ermittelt werden. Somit kann man z.B. erhebliche Kosten sparen, sofern man nur an den obersten Elementen des Stacks interessiert ist (JEP 259)

HTTP/2

Java 9 führt ein neues HTTP client API ein, das HTTP/2 und WebSocket implementiert und ersetzt somit das alte HttpURLConnection-API. Das neue API wird als Incubator-Modul ausgeliefert (es ist also noch offiziell Bestandteil der SE) (JEP 110)

Multi-release jars

Das jar-Dateiformat erlaubt nun die Koexistenz mehrerer Java-Release-spezifischer Versionen von class-Dateien (“multi-realease jar-files”). Dieses neue Feature verlangte natürlich auch die Erweiterung des jar-Tools (JEP 238)

GC

Als Garbage-Collector wird nun standardmäßig der G1 verwendet (dieser Collector eignet sich für große Heaps und mimimiert die Häufigkeit von Pausen). Er ersetzt somit den bislang verwendeten Parallel GC (JEP 248).

Compact Strings

Der Speicherverbrauch eines Programms wird maßgeblich von der Speicherung von Strings bestimmt. Strings wurden bislang mittels Arrays von chars implementiert – und ein char umfasst bekanntlich 2 Byte. In den meisten Fällen reicht aber ein einziges Byte zur Speicherung eines Zeichens aus. In Java 9 werden Strings nun – sofern möglich – platzsparender gespeichert (“compact strings”) (JEP 254).

Parser-API für JavaScript

Der Java-Script-Interpreter Nashorn wurde um ein Parser-API erweitert. Das API erlaubt die Visitor-basierte Inspektion eines JavaScript-AST-Baumes (JEP 236)

Diamond-Operator

Bislang war die Benutzung des “Diamond-Operators” beschränkt: bei der Definition von anonymen Klassen konnte er nicht verwendet werden. Diese Beschränkung ist nun aufgehoben worden (auch Kleinigkeiten sind wichtig…).

Für wen hat sich das Warten gelohnt? Für alle!

Was sich Entwickler jetzt noch wünschen? Das Modul-Konzept wird von einigen als zu statisch kritisiert. Kritisiert wird zuweilen auch, dass Module nicht geschachtelt werden können.

 

Seminare zu Java 9:

https://www.integrata-cegos.de/seminarangebot/anwendungsentwicklung/neuerungen-java-9-und-jigsaw/

Weiterlesen