Java 9 – Project Jigsaw
Das Release von Java 9 ist für den 27. Juli 2017 vorgesehen. Mit Version 8 hat Java eine Menge neuer Features bekommen, von denen sicherlich die Unterstützung von Lambdas die weitesten Kreise gezogen hat. Aber auch die dadurch möglichen Bulk Operations auf Collections und das neue Date-Time-API verbessern das tägliche Leben des Java-Entwicklers. Ein lange gewünschtes Feature hat es allerdings nicht mehr in Java 8 geschafft und soll nun das Markenzeichen der kommenden Version 9 werden – die Modularisierung mit Project Jigsaw.
Project Jigsaw soll zwei Probleme angehen, unter denen Java bisher leidet, die JAR-Hölle und das Fehlen eines starken Kapselungsmechanismus oberhalb von Klassen. Von Anfang hatte Java ein Package-Konstrukt. Eine Klasse kann innerhalb eines Pakets eine von zwei Sichtbarkeiten haben. Entweder sie ist public, dann kann von überall auf die Klasse zugegriffen werden. Wenn sie nicht public ist, kann nur von innerhalb des Packages auf sie zugegriffen werden. Aber Packages können nicht geschachtelt werden. Das führt dazu, dass man entweder unstrukturierte große „Big Ball of Mud“-Pakete hat oder solche, die nur aus öffentlichen Klassen bestehen. JARs (Java Archives) wiederum sind nur eine Menge von komprimierten Class-Dateien plus Daten. Sie sind keine Komponenten und bieten keine Kapselung. Deshalb haben sie auch keine Schnittstelle oder besser gesagt ist die Schnittstelle eines JARs alles, was das JAR enthält. Denn es kann ja wegen fehlender Kapselung nichts vor dem Zugriff von außen verstecken. Mit Version 9 erhält Java die Möglichkeit, Module zu definieren. Ein Modul ist eine benannte, selbstbeschreibende Programmkomponente, die aus einem oder mehreren Paketen (und Daten) besteht. Module lassen sich wie in Listing 1 definieren.
Listing 1: Ein Java-Modul"]module de.modul.a { exports de.modul.a.paket.x; } module de.modul.b { exports de.modul.a.paket.y; exports de.modul.a.paket.z; } module de.modul.c { requires de.modul.a; requires de.modul.b; }
Mit dieser Schnittstellendefinition wird bekanntgegeben, welche Packages ein Modul nach außen anbietet (mit dem Schlüsselwort exports) und welche Module es von außen benötigt (mit dem Schlüsselwort requires). Achtung: Das ist kein Tippfehler im vorherigen Satz; ein Modul exportiert Packages, benötigt aber Module. Das kann auf den ersten Blick verwirrend sein, da Pakete und Module per Konvention gleiche oder sehr ähnliche Namen haben. Alle Pakete eines Moduls, die nicht explizit exportiert werden, lassen sich nur innerhalb des Moduls verwenden. Wird versucht von außerhalb des Moduls auf sie zuzugreifen, kommt es zu einem Compilerfehler.
modulares JAR (engl. modular JAR) zu erzeugen. Es ist wie eine herkömmliche JAR-Datei aufgebaut, mit dem Unterschied, dass es eine Datei module-info.class in seinem Wurzelverzeichnis hat. So ein modulares JAR lässt sich als Modul verwenden. Aus Gründen der Abwärtskompatibilität kann es auch als klassisches JAR-File und in einem classpath zum Einsatz kommen. Dann wird die module-info.class einfach ignoriert. Apropos classpath: Mit der Einführung des Modulkonzepts wird er durch einen modulepath ersetzt. Im modulepath kann dann angegeben werden, wo im Dateisystem bestimmte Module gefunden werden können.
Der Core JAVA & JVM Languages Track auf der W-JAX 2018
Früher hatte man einen classpath mit einem Haufen ungeordneter JARs, die sich unkontrolliert gegenseitig benutzen konnten. Außerdem durfte auf alles innerhalb der JARs zugegriffen werden. Nun können wir mit dem Modulmechanismus klar definieren, welches Modul welche anderen Module verwenden soll und kann. So wird es auch endlich möglich, mehrere Versionen der gleichen Bibliothek parallel zu verwenden. So kann ein Modul A die Bibliothek in Version 1 nutzen, Modul B in Version 2 und schließlich das Modul C die beiden Module A und B.
Java 9: Architektur endlich in der Sprache ausdrücken
Mit dem Modulkonzept kann man die Architektur von Software viel besser ausdrücken. So können z. B. Schichten als Module dargestellt und ihre Schnittstellen klar definiert werden. Architekturverletzungen kann zumindest teilweise schon der Compiler finden und verhindern. Betrachten wir als Beispiel eine Bankanwendung, die mit Domain-driven Design entworfen ist (Listing 2 und Abb. 1).
Listing 2: Beispiel Bankanwendung"]module de.wps.bankprogramm.domainLayer { exports de.wps.bankprogramm.domainLayer.valueObject; exports de.wps.bankprogramm.domainLayer.entity; } module de.wps.bankprogramm.infrastructurelayer { exports de.wps.bankprogramm.infrastructureLayer.database; } module de.wps.bankprogramm.applicationLayer { requires de.wps.bankprogramm.infrastructureLayer; requires de.wps.bankprogramm.domainLayer; exports de.wps.bankprogramm.applicationLayer.repositories; } module de.wps.bankprogramm.uiLayer { requires de.wps.bankprogramm.domainLayer; requires de.wps.bankprogramm.applicationLayer; }
Hier sind die vier Schichten des Systems als Module implementiert. Das Modul der Fachlogikschicht (d. h. das Modul domainLayer) ist so deklariert, dass es keine Abhängigkeiten zu anderen Modulen hat. Wir wollen unseren fachlichen Code ja nicht mit Abhängigkeiten zu technischem Code verschmutzen. Es enthält ein Paket für die entities unseres Systems und eines für seine value objects. Die repositories wiederum dürfen auf die Infrastrukturschicht (Modul infrastructureLayer) zugreifen. Deshalb sind sie in diesem Entwurf in das Modul der Anwendungsschicht (applicationLayer) gesteckt. Dieses darf nach obiger Deklaration auf Infrastruktur- und Fachlogikschicht zugreifen. Die Oberflächenschicht (Modul uiLayer) darf dann auf Fachlogik- und Applikationsschicht zugreifen. Eine Nutzung des Pakets mit dem Datenbankzugriffcode würde einen Compilerfehler ergeben, weil dieser im Infrastrukturpaket liegt und es nicht in den requires von uiLayer angegeben wurde. Die Zuordnung der repositories in die Anwendungsschicht ist architektonisch nicht ganz sauber, wurde hier aber vorgenommen, um das Beispiel nicht zu kompliziert zu machen.
Das JDK zerlegt sich selbst
Der Modulmechanismus ist für viele Projekte interessant, insbesondere aber auch für das JDK selbst. Daher kommt auch der Name des Projekts, der auf Deutsch „Stichsäge“ lautet. Und genau mit dieser Stichsäge soll Java auch in Module aufgeteilt werden. Denn bisher muss immer das ganze JRE ausgeliefert werden, auch wenn nur kleine Programme laufen sollen, die z. B. vielleicht gar kein GUI haben oder nicht auf eine Datenbank zugreifen. Mit Java 9 werden JRE und JDK selbst in Module zerlegt. So kann jedes Programm definieren, welche es benötigt, und Speicherverbrauch verringern und Performance erhöhen.
Verschiedene solcher Java-Standardmodule sind geplant, z. B. java.base, java.sql, java.desktop und java.xml. Klar ist jetzt schon, dass java.base immer implizit eingebunden wird – so wie das Package java.lang nicht extra importiert werden muss. Das Modul java.base wird u. a. die Pakete java.lang, java.math und java.io enthalten. Für die Module des JDK selbst reichen JAR-Dateien nicht aus, denn sie müssen z. B. auch nativen Code enthalten. Deshalb wurden hier die so genannten JMOD-Dateien eingeführt. O-Ton Mark Reinhold, Chefarchitekt von Java: „JMOD files are JAR files on steroids.“ Project Jigsaw ist sicherlich die große Änderung, die mit Java 9 kommen wird, und ihr Markenzeichen. Aber es gibt auch noch eine ganze Reihe weiterer Features, die das Entwicklerleben verbessern werden.
Was passiert sonst noch in Java 9?
Viele Programmiersprachen haben eine Read-Eval-Print-Loop (kurz REPL), d. h. eine Art Kommandozeile, die direkt Code in dieser Sprache ausführt und das Ergebnis ausgibt. Java hatte so etwas bisher im Standard-JDK nicht. Es gibt Third-Party-Produkte wie BeanShell oder Java REPL und ein Plug-in für IntelliJ IDEA. Das Projekt Kulla führt nun die JShell ins JDK ein – die offizielle REPL für Java. Damit erhofft man sich, dass sich Java leichter lernen lässt. Denn ein interaktiver Modus kann dem Programmierer viel schnelleres Feedback geben als der klassische Schreiben-/Kompilieren-/Ausführen-Zyklus. Es wird ein CLI-Programm jshell für die Kommandozeile geben. Außerdem soll ein API bereitgestellt werden, damit auch andere Anwendungen diese Funktionalität verwenden können. Dies ist insbesondere für die IDE-Hersteller interessant, welche die JShell in Eclipse, NetBeans und Co. einbauen können.
Der Core JAVA & JVM Languages Track auf der W-JAX 2018
Unterstützt Unicode
Um die Zeichen verschiedener Sprachen zu kodieren, existiert Unicode. Der Standard wird laufend erweitert, und Java fehlt bisher die Unterstützung für die letzten beiden Releases 7.0 und 8.0. Unicode 7.0 enthält u. a. Verbesserungen für bidirektionale Texte, also solche, die Abschnitte sowohl in lateinischer als auch in nicht lateinischer Schrift enthalten. Mit Version 8.0 werden z. B. die Emojis um Smileys in verschiedene Hautfarben und neue Gesichter wie das der Weihnachtsfrau (Mother Christmas) erweitert. Des Weiteren schafft ein eigener JEP (Nr. 226) die Möglichkeit, Property-Dateien in UTF-8 zu speichern. Bisher wurde nur ISO 8859-1 als Encoding unterstützt. Dazu wird das RessourceBundle-API erweitert.
Bequeme Collections erzeugen
Möchte man mehrere Objekte zusammen definieren, so geht das mit einem Array einfach.
String[] vornamen = { "Hinz", "Kunz", "Fritz" };
Mit Collections ist es bisher leider nicht so einfach möglich. Um eine kleine unveränderliche Collection zu erzeugen, muss sie konstruiert und zugewiesen werden, dann die Element hinzugefügt und schließlich ein Wrapper drumherum gebaut werden.
List<String> vornamenListe = new ArrayList<>(); vornamenListe.add("Hinz"); vornamenListe.add("Kunz"); vornamenListe.add("Fritz"); vornamenListe = Collections.unmodifiableList(vornamenListe);
Statt einer Zeile Quellcode haben wir hier auf einmal fünf. Außerdem kann man es nicht als einen einzelnen Ausdruck ausdrücken. Es gibt verschiedene Alternativen, z. B. mit Arrays.asList(). Aber wenn man damit eine Menge definieren will, wird es doch wieder ziemlich lang:
Set<String> vornamenMenge = Collections.unmodifiableSet( new HashSet(Arrays.asList("Hinz", "Kunz", "Fritz")));
Deshalb werden mit Java 9 Bequemlichkeitsmethoden eingeführt, mit denen sich Ähnliches leichter ausdrücken lässt.
List<String> vornamenListe = List.of("Hinz", "Kunz", "Fritz");
Mithilfe von varargs wird es möglich sein, eine verschiedene Anzahl von Parametern an diese Fabrikmethoden zu übergeben. Diese Funktionalität wird für Set und List angeboten und in ähnlicher Form auch für Map. Durch die erst mit Java 8 eingeführten Methodenimplementierungen in Interfaces, den so genannten Default-Methoden ist es möglich, diese Bequemlichkeitsmethoden direkt in den Interfaces List, Set und Map zu definieren.
HTTP/2 ist mit dabei
HTTP, das Protokoll zur Übertragung von Webseiten, wurde in seiner aktuellen Version 1.1 schon 1997 verabschiedet. Erst 2015 wurde dann die neue Version 2 zum Standard erhoben. Ziel der neuen Fassung ist die Verringerung der Latenz, um so zu ermöglichen, dass Webseiten schneller geladen werden können. Dies wird durch verschiedene Techniken erreicht:
- Kompression der Header
- Server-Push
- Pipelining
- Multiplexen von mehreren HTTP-Requests über eine TCP-Verbindung
Dabei bleibt die Kompatibilität zu HTTP 1.1 gewahrt. Große Teile der Syntax bleiben sogar unverändert; so z. B. die Methoden (GET, PUT, POST usw.), der URI, Statuscodes und Headerfelder.
Java wird mit der Implementierung von JEP 110 Out-of-the-box-Unterstützung von HTTP/2 erhalten. Außerdem wird das veraltete HttpURLConnection-API ersetzt. Es entstand noch zu Zeiten von HTTP 1.0 und hat einen protokollagnostischen Ansatz gewählt. Das passte in die Neunziger, als noch nicht klar war, wie erfolgreich HTTP sein würde. Heutzutage ist allerdings Unterstützung für beispielsweise Gopher weniger wichtig. Des Weiteren soll ALPN unterstützt werden. In der neuen Welt kann man dann ein zeitgemäßes Fluent-API verwenden.
HttpResponse response = HttpRequest .create(new URI("http://www.javamagazin.de") .body(noBody()) .GET() .send();
An der so erhaltenen HTTP-Antwort können dann Statuscode und Inhalt abgefragt werden:
int statusCode = response.responseCode(); String body = response.body(asString());
Kompakte Strings brauchen weniger Speicher
Seit Version 1 werden Zeichenketten in Java mithilfe der Klasse java.lang.String dargestellt. Von Anfang an enthielt diese Klasse ein Array von char. Dieser Datentyp belegt in Java zwei Bytes. So lassen sich Zeichen in UTF-16 bequem darstellen und es wird nicht nur das lateinische Alphabet unterstützt. Allerdings werden in vielen Anwendungen nur Zeichen aus der Kodierung Latin-1 verwendet, die nur ein Byte benötigen. Hier ist jedes zweite Byte leer und vergeudet Speicherplatz. JEP 254 führt daher eine Implementierung der String-Klasse ein, die statt eines char-Arrays ein byte-Array plus ein Kodierungsfeld enthält. In dem Kodierungsfeld wird angegeben, ob der String entweder eine klassische Reihenfolge von UTF-16-Zeichen enthält, die jeweils zwei Bytes belegen, oder ob er eine Reihe von Latin-1-Zeichen enthält, die dann nur jeweils ein Byte belegen. In welcher Kodierung der jeweilige String angelegt wird, soll automatisch am Inhalt der Zeichenkette erkannt werden.
Das Angenehme an dieser Optimierung ist, dass man ganz automatisch davon profitiert. Es handelt sich nämlich um ein reines Implementierungsdetail, das hundertprozentige Kompatibilität mit alten Java-Versionen wahrt. Anwendungen, die viele Strings verwenden, werden so ihren Speicherbedarf deutlich senken – und das durch einfaches Installieren der neuesten Version von Java. Dazu werden neben der Klasse String auch verwandte Klassen wie StringBuilder und StringBuffer sowie die HotSpot VM angepasst.
Erweiterungen an JavaDoc
Aus Javadoc wird bisher HTML in der in die Jahre gekommenen Version 4.01 erzeugt. Mit Java 9 wird es möglich sein, auch HTML5 zu erzeugen. Dazu soll das Kommando javadoc einen Switch erhalten, mit dem die Version des generierten HTML-Codes angegeben werden kann. Ausdrückliches Nichtziel (non-goal) des zugehörigen JEP 224 ist es, die Struktur mit den drei Frames abzuschaffen. Bleibt zu hoffen, dass das mit einer zukünftigen Fassung geschehen wird. Des Weiteren sollen die generierten HTML-Seiten einen Suchmechanismus bekommen, mit dem nach bestimmten Java-Elementen gesucht werden kann. Die Ergebnisse werden dann kategorisiert z. B. nach „Modules“, „Package“ oder „Types“.
HiDPI-Grafiken: Anzeige skaliert automatisch
Auf dem Mac unterstützt das JDK bereits Retina-Displays, unter Linux und Windows bisher nicht. Dort sehen Java-Programme auf den aktuellen hochauflösenden Bildschirmen unter Umständen so klein aus, dass sie sich nicht verwenden lassen. Das liegt daran, dass auf diesen Systemen Pixel zu Größenberechnung verwendet werden – unabhängig davon wie groß ein Pixel tatsächlich ist. Und der Witz von hochauflösenden Displays ist eben, dass Pixel sehr klein sind. JEP 263 erweitert das JDK so, dass auch unter Windows und Linux die Größe von Pixeln berücksichtigt wird. Dazu werden modernere APIs als bisher verwendet: Direct2D unter Windows und GTK+ statt Xlib unter Linux. Damit werden Fenster, Grafiken und Text automatisch skaliert. JEP 251 schafft außerdem die Möglichkeit, so genannte Multi-Resolution Images zu verarbeiten, d. h. Dateien, die dasselbe Bild in unterschiedlichen Auflösungen enthalten. Abhängig von der DPI-Metrik des aktuellen Bildschirms wird dann das Bild in der jeweils passenden Auflösung verwendet.
Free: Mehr als 40 Seiten Java-Wissen
Lesen Sie 12 Artikel zu Java Enterprise, Software-Architektur und Docker und lernen Sie von W-JAX-Speakern wie Uwe Friedrichsen, Manfred Steyer und Roland Huß.
Was sonst noch kommt
Wie jedes Java-Release enthält auch das mit der Version 9 eine Reihe von Kleinigkeiten und Aktualisierungen. Dazu gehören:
- Die neue ARM-Architektur AArch64, die ARM-Prozessoren in die 64-Bit-Architektur hebt, wird nun unterstützt.
- Java benutzt seit Version 1.2 ein proprietäres eigenes Format, mit dem kryptografische Schlüssel gespeichert werden können: JKS. JEP 229 führt nun das Standarddateiformat PKCS12 in Java ein.
- Update 40 von Java 8 führte den Garbage Collector G1 (Garbage First) ein. Java 9 erhebt G1 nun in den Status des Standard-Garbage-Collectors.
- Bis Java 8 unterstützt das Image-I/O-Framework nicht das Bildformat TIFF. Das wird mit JEP 262 geändert und javax.imageio entsprechend erweitert.
- Mit Nashorn hat Java eine JavaScript-Ausführungsumgebung. Für IDEs und ähnliche Werkzeuge wird mit JEP 236 das bisher nur intern verfügbare API des Parsers für Zugriffe auf den AST öffentlich gemacht.
- Seit zwanzig Jahren beginnt die interne Versionsnummer von Java mit 1. Das soll nun geändert werden.
Fazit und Ausblick
Ursprünglich sollte dieser Artikel beginnen mit: “Im September 2016 wird die neue Java Version 9 erscheinen.” Doch wurde der Release-Termin von Java 9 mittlerweile auf Juli 2017 verschoben. Das ist sicher zu verschmerzen, denn viele der Änderungen fallen in die Kategorie „Housekeeping“. So werden dann die aktuellen Versionen der Standards Unicode und HTTP unterstützt. Außerdem gibt es kleinere Änderungen, die das Entwicklerleben vereinfachen, wie das bequeme Erzeugen von Collections und die Implementierung von kompakten Strings.
Das wichtigste Feature ist ganz klar das Modulkonzept von Jigsaw. Davon profitieren werden vor allem große Projekte und solche, bei denen der Speicherbedarf eine Rolle spielt. Große Projekte, weil erstens das Problem der JAR-Hölle mit dem ModulePath gelöst wird und zweitens die Architektur dieser Systeme mit Modulen klarer ausgedrückt werden kann. Speichersensitive Projekte profitieren, weil das JDK selbst in Module aufgeteilt wird und nicht mehr als Monolith im Ganzen geladen werden muss.
Spannend ist auch der Blick auf das, was nach Java 9 kommen wird. Als größtes Feature sind für Java 10 Value Types angekündigt. Damit werden benutzerdefinierte Werttypen möglich. Aber erstmal freuen wir uns auf Java 9!
Erfahren Sie mehr zu Core JAVA & JVM Languages auf der W-JAX 2018
● Spring Framework 5.1 on JDK 8 and 11
● https://jax.de/core-java-jvm-languages/java-apis-the-missing-manual/