Java 9 - Die Neuerungen im Überblick (PDF)

Eine Einführung in das Modulsystem und weitere Neuerungen mit Java 9

Autor:
Steffen Jacobs
Orientation in Objects GmbH
Steffen Jacobs
Steffen Jacobs
Datum:Juni 2018

Java 9 - Die Neuerungen im Überblick

Mit dem Release von Java 9 hat sich in der Java-Welt einiges verändert: Es gibt erstmals ein Modulsystem, welches das JDK in viele kleinere, voneinander abgekapselte Päckchen aufteilt. Um dieses Modulsystem richtig verwenden zu können, ist es höchst relevant, über die Motivation und die Funktionsweise dieses Modulsystems im Java-Kontext Bescheid zu wissen.

Neben dem Modulsystem gab es mit Java 9 auch noch eine größere Menge an Änderungen an den Standardbibliotheken und den zusammen mit dem JDK ausgelieferten Tools. Hierzu gibt es weiter unten eine Übersicht mit weiterführenden Quellen.

Zusätzlich zu all den Änderungen an der Java-Sprache und der Java Plattform gibt es auch organisatorische Neuigkeiten. Für einen Java-Entwickler, insbesondere im Enterprise-Umfeld, ist dabei das neue Java Release Modell höchst relevant, besonders in Bezug auf die Änderungen im Bereich Long-Term-Support. Dieses neue Modell hat weitreichende Implikationen für die nächsten Java-Releases und deren Funktionsumfang, die am Ende des Artikels beschrieben werden. Abschließend folgt noch ein kurzer Ausblick auf Java 10 und Java 11.

Das Modulsystem

Die wahrscheinlich größte und relevanteste Änderung mit Java 9 ist das neue Modulsystem. Dieses Java-Modulsystem (Codename: Jigsaw) wurde initial mit Java 7 angekündigt. Die Einführung hat sich jedoch zunächst bis Java 8 und dann bis Java 9 verzögert. Das Primärziel des Java-Modulsystems ist es, die Java Standard Edition (JSE) und das Java Development Kit insbesondere im Hinblick auf leistungsschwache Endgeräte skalierbarer zu machen. Außerdem soll das Modulsystem dabei helfen, die Erstellung und Wartung von Programmbibliotheken und großen Anwendungen zu verbessern. Gleichzeitig soll die Sicherheit und Performance durch die Abkapselung der JDK-Elemente in Module gesteigert werden. Das Modulsystem soll zuverlässig sein, eine starke Kapselung durchsetzen, die Java Plattform skalierbarer machen und eine bessere Plattformintegrität garantieren. [JGSW, JMOD, JGSWO]

Die Motivation

Wofür wird das Java-Modulsystem denn nun in einer eigenen Anwendung gebraucht? Diese Frage soll hier anhand eines Beispiels beantwortet werden.

Angenommen, es ginge um die Entwicklung einer browserbasierten Enterprise Application. Die erste Unterteilung, die man hierbei vornehmen könnte, ist das Projekt auf Build-Ebene in Frontend und Backend zu unterteilen. Dazu könnte man in Java etwa zwei separate Packages anlegen: Eins für das Frontend und eins für das Backend. Theoretisch würde man zwischen Frontend und Backend nur über eine wohldefinierte, fixierte Menge an Schnittstellen kommunizieren wollen. Alle anderen internen Klassen, die Implementierungsdetails zu Frontend und Backend enthalten, sollten außerhalb der entsprechenden Packages nicht sichtbar sein.

Das Problem hierbei ist, dass alle Klassen, die public sind, im jeweils anderen Package trotzdem sichtbar sind. Um das zu lösen, könnte man komplett auf Unterpackages verzichten und sämtliche Klassen in dem jeweiligen Package auf package-private setzen. Hierbei würde die Übersicht allerdings sehr schnell verloren gehen.

Viel angenehmer ist hier ein Modulsystem, welches sämtliche Zugriffe von einem fremden Package sperren kann. Eine solche Lösung präsentiert das neue Modulsystem in Java 9.

Hierbei wird durch das Modulsystem eine Art Filter oberhalb von public einfügt. In jedem Modul können Schnittstellen definiert werden, die von außen sichtbar sind. Außerdem kann festgelegt werden, welche anderen Module zur Ausführung benötigt werden. Somit sind selbst Klassen, die in einem Package public sind, nicht zwingend von jedem anderen Package aus sichtbar. [JMOD]

Wie funktioniert das neue Modulsystem

Normalerweise ist jede öffentliche Klasse einer Bibliothek oder eines Anwendungsprojekts von außen für alle sichtbar. Jetzt kann ausgewählt werden, welche Klassen oder Schnittstellen für den Endbenutzer sichtbar und welche Klassen unsichtbar sein sollen.

Der Moduldeskriptor (module-info.java)

Dazu muss lediglich ein Moduldeskriptor (module-info.java -Datei) in das Projekt eingefügt werden [JGSWQS]. Ein erster, einfacher Moduldeskriptor ist im folgenden Listing zu sehen:

module de.oio.firstModule {    
}

Beispiel 1: Einfache Moduldefinition des Moduls de.oio.firstModule in der Datei module-info.java

Wie im Listing oben zu erkennen, enthält dieser Deskriptor den Modulnamen und zwei geschweifte Klammern.

Im nächsten Schritt sollen nun die Abhängigkeiten des Moduls firstModule über den Moduldeskriptor in der module-info.java dargestellt werden. Dazu verwenden wir das Schlüsselwort requires gefolgt von dem Namen des Moduls, welches importiert werden soll. In diesem Beispiel werden die beiden Module someModule und otherModule importiert.

module de.oio.firstModule {    
    requires de.oio.someModule;
    requires de.oio.otherModule;
}

Beispiel 2: Moduldefinition mit Abhängigkeiten

Im letzten Schritt soll nun spezifiziert werden, welche Packages aus unserem firstModule nach außen sichtbar gemacht werden sollen. Dazu wird die exports -Klausel verwendet.

module de.oio.firstModule {    
    requires de.oio.someModule;
    requires de.oio.otherModule;
	
    exports de.oio.firstModule.api;
}

Beispiel 3: Moduldefinition mit Abhängigkeiten und Exporten

Unser Modul heißt nun also de.oio.firstModule. Es bestehen Abhängigkeiten von de.oio.someModule und de.oio.otherModule. Nach außen verfügbar gemacht werden alle Klassen im Package de.oio.firstModule.api. Sämtliche Klassen, die sich nicht im de.oio.firstModule.api -Package befinden, sind von außen weder sichtbar, noch über die Reflection-API abrufbar. [REFL]

Zusätzlich könnte noch spezifiziert werden, wohin die Klassen exportiert werden. So können beispielsweise einige Klassen für alle Module exportiert werden, während andere Klassen nur für konkrete andere Module exportiert werden sollen. Dazu ein Beispiel:

module de.oio.firstModule {    	
    exports de.oio.firstModule.api;
    exports de.oio.firstModule.internalApi to de.oio.secondModule;
}

Beispiel 4: Moduldefinition mit Exporten an konkretes anderes Modul

In diesem Beispiel wird etwa das Package de.oio.firstModule.internalApi nur an das Module de.oio.secondModule exportiert.

Transitive Abhängigkeiten

Nun ein weiteres kurzes Beispiel, dieses Mal mit transitiven Abhängigkeiten. Die Abhängigkeiten zwischen den Modulen A bis D sind in der folgenden Abbildung visualisiert:

Abhängigkeiten der Module A bis D

Abbildung 1: Abhängigkeiten der Module A bis D

In diesem Beispiel hängt Modul A von Modul B ab und Modul B von den Modulen C und D.

Hierbei ist zu beachten, dass der Abhängigkeitsgraph, der durch die requirements beschrieben wird, standardmäßig nicht transitiv ist. Daher hängt in unserem Beispiel A selbst nicht direkt von C oder D ab. Somit kann es hier zu einem Compilerfehler kommen. Um dieses Problem zu lösen gibt es zwei mögliche Ansätze:

1. Jede weitere Abhängigkeit wird explizit direkt im Hauptmodul definiert (sprich ein requires C und requires D im Moduldeskriptor von Modul A ). Beispiel:

module de.oio.A {    	
    requires de.oio.B;
	
    requires de.oio.C;	
    requires de.oio.D;
}

Beispiel 5: Moduldefinition für Ansatz 1 (Direkte Abhängigkeit)

2. Im Moduldeskriptor von Modul A wird statt des Schlüsselworts requires B der Satz requires transitive B verwendet. Diese "Transitivität" geht allerdings nur eine Ebene tief. Sollte jetzt etwa Modul D von einem weiteren Modul E abhängen, so muss das Schlüsselwort transitive eine Ebene tiefer wiederholt werden. Beispiel:

module de.oio.A {    	
    requires transitive de.oio.B;
}

Beispiel 6: Moduldefinitionen für Ansatz 2 (Transitive Abhängigkeit)

Nun können auch die exportierten Klassen aus den Modulen C und D im selbst erstellten Modul A verwendet werden.

Reflection-Zugriff auf fremde Module

Um (beispielsweise via Reflection-API) auch auf die internen Werte und Klassen zugreifen zu können, wird ein weiteres Schlüsselwort benötigt, nämlich opens. In diesem Beispiel soll das Modul A per Reflection-API auf Modul B zugreifen. Dafür muss der Moduldeskriptor von Modul B wie folgt angepasst werden:

module de.oio.B {    	
    opens de.oio.B.internal;
    exports de.oio.B.internal;
}

Beispiel 7: Angepasste Moduldefinitionen von Modul B für Reflection-Zugriff von Modul A auf Package de.oio.B.internal

Nun kann Modul A per Reflection-API auf die internen Klassen und Werte im Package de.oio.B.internal im Modul B zugreifen. Im Übrigen ersetzt das Schlüsselwort opens das bereits bekannte Schlüsselwort exports nicht. Vielmehr ermöglicht das Schlüsselwort opens einen Zugriff per Reflection-API, während exports einen Zugriff auf die public Klassen auf Sprachebene erlaubt.

Das opens -Schlüsselwort kann, genau wie exports auch, ausschließlich an ein spezifiziertes Modul exportieren:

module de.oio.B {    	
    opens de.oio.B.internal to de.oio.A;
    exports de.oio.B.internal to de.oio.A;
}

Beispiel 8: opens -Schlüsselwort an konkretes Modul

Der Module-Path

Da ein Java Programm auf der obersten Ebene nun nicht mehr direkt aus Klassen, sondern aus Modulen besteht, die diese Klassen enthalten, wird statt des alten Klassenpfads nun ein Modulpfad benötigt [SMOD]. Dieser neue Modulpfad ist weitaus robuster als der Klassenpfad, da bereits beim Auflösen des Modulgraphen bekannt ist, wenn Module fehlen oder zwei Module den gleichen Namen haben. Derartige Probleme traten bisher erst zur Laufzeit auf und resultierten beispielsweise in einer ClassNotFoundException. Da der Modulgraph bereits beim Compile-Vorgang und beim Start jeder Anwendung aufgelöst wird, fällt dieser Fehler viel früher auf und wird nicht erst sichtbar, wenn die betroffene Codestelle ausgeführt wird.

Das Unnamed und das Automatic Module

Aus Kompatibilitätsgründen mit alten Java Projekten und JAR-Dateien ist die manuelle Erstellung einer module-info.java -Datei theoretisch optional. Sofern keine module-info.java -Datei gefunden wurde, kann ein Unnamed Module oder ein Automatic Module automatisch erstellt werden.

In das Unnamed Module werden sämtliche Packages eingefügt, die über den Classpath importiert wurden. Da das Unnamed Module, wie der Name schon sagt, keinen Namen besitzt, können andere Module nicht auf dieses Modul verweisen. Außerdem werden für das Unnamed Module alle anderen Module, die zur Verfügung stehen, importiert.

Das Automatic Module wird erstellt, wenn beispielsweise eine JAR-Datei über den Module-Path eingeladen wurde. Der Modulname wird hier aus dem Namen der JAR-Datei erschlossen. Sofern eine Versionsbezeichnung im Dateinamen vorhanden ist, wird diese vorher entfernt. Wie auch das Unnamed Module, importiert das Automatic Module alle zur Verfügung stehenden anderen Module. Zusätzlich werden hier alle internen Packages exportiert.

Die Classdateien in den jeweiligen Modulen werden dabei nicht analysiert. Daher kann es beim Starten eines der generierten Module zu ClassNotFoundExceptions kommen.

Dieses beinhaltet den gesamten alten Classpath. Als Importanforderung (requires) werden einfach alle Module aus dem JDK angefordert. Bei den Exporten (exports) werden alle Klassen des Moduls nach außen exportiert und damit für alle zugreifbar gemacht.

Das Modulsystem in der Praxis

Nach dieser theoretischen Einführung in das Modulsystem nun ein kleines Beispiel mit zwei Modulen. Das erste Modul beinhaltet eine einfache Java Anwendung und das zweite Modul eine minimale Bibliothek mit einer ausgelagerten Funktion. Die Java Anwendung besteht nur aus einer Main-Klasse, während die Bibliothek aus einer API und einer internen Implementierung besteht. Die beiden Module sind hier noch einmal anhand ihrer Dateistruktur visualisiert:

Java9Jigsaw/
├──de.oio.java9lib
│   ├── de
│   │   └── oio
│   │       └── java9lib
│   │           ├── export
│   │           │   └── SomeAPIInterface.java
│   │           │   
│   │           └── internal
│   │               └── InternalImpl.java
│   └── module-info.java
│
└──de.oio.java9app
    ├── de
    │   └── oio
    │       └── java9app 
    │           └── Main.java
    └── module-info.java

Beispiel 9: Dateistruktur

Die Klasse in InternalImpl.java:

public class InternalImpl implements SomeAPIInterface {
 
    public int someValue = 5;
 
    @Override
    public void doStuff() {
        // do important stuff
    }
}

Beispiel 10: Inhalt der Klasse InternalImpl

Die InternalImpl -Klasse besitzt eine öffentliche Variable someValue vom Typ Integer, die auf 5 gesetzt ist. Außerdem überschreibt sie die doStuff() -Methode des Interfaces SomeAPIInterface.

Die Implementierung der Main -Klasse sieht wie folgt aus:

import (...)
 
public class Main {
    public static void main(String... args) {
        /* working as expected */
        SomeAPIInterface interf;
 
        /* not working */
        InternalImpl impl = new InternalImpl();
 
        /* not working either */
        int value = impl.someValue;
    }
}

Beispiel 11: Inhalt der Main -Klasse

Nun die Moduldeskriptoren. Zunächst der Moduldeskriptor für das Bibliotheksmodul:

module de.oio.java9lib {    
    exports de.oio.java9lib.export; 
}

Beispiel 12: Moduldefinition des Bibliotheksmoduls

Wie aus der module-info.java -Datei hervorgeht, ist der Name des Moduls de.oio.java9lib (erste Zeile). Es wird nur das Package de.oio.java9lib.export exportiert. Im Bild der Dateistruktur oben ist zu sehen, dass sich in diesem Package nur das Interface SomeAPIInterface befindet. Die Klasse InternalImpl befindet sich in einem anderen Package und wird hier nicht exportiert. Daher wird sie später auch nicht zugreifbar sein.

Der Moduldeskriptor für die Hauptanwendung sieht wie folgt aus:

module de.oio.java9app {    
    requires de.oio.java9lib;
}

Beispiel 13: Moduldefinition des Bibliotheksmoduls

Wie zu erwarten, sieht die Main-Klasse nun zwar das Interface SomeAPIInterface und kann korrekt eine Variable von diesem Typ deklarieren. Die nicht exportierte Klasse InternalImpl ist dagegen weder sichtbar, noch lässt sich darauf zugreifen. Die Zeilen 9 und 11 geben bereits zur Compile-Zeit einen Fehler.

Hier wird das oben definierte Modul de.oio.java9lib in das Modul de.oio.java9app (die Hauptanwendung) importiert.

Kritik

Was noch fehlt, wäre eine standardisierte Versionierung der Module. Diese ist auch nicht mit einem in anderen Quellen beschriebenen Workaround nachrüstbar. Hierbei wurde vorgeschlagen, für unterschiedliche Versionen der gleichen Bibliothek einfach mehrere JARs mit unterschiedlichen Dateinamen (etwa Library-v1.0, Library-v1.1) eingebunden werden. Dies funktioniert nicht. Beim Laden von JAR-Dateien wird nämlich zunächst einmal ein gegebenenfalls existierender Versionszusatz entfernt. Außerdem werden Klassen aus dem Classpath ja wie oben beschrieben in das separate Unnamed-Module geladen. Da es nicht möglich ist, mehrmals das gleiche Package aus unterschiedlichen Quellen zu importieren, gibt es spätestens hier einen Fehler oder unerwartetes Verhalten.

Im Kontrast dazu ist es möglich, unterschiedliche Versionen einer Bibliothek oder eines allgemeinen Programms für unterschiedliche Java-Versionen vorzuhalten. Mehr dazu im Abschnitt Multi-Release-JARs.

Außerdem müssen alle Module beim Java-Modulsystem zur Compile-Zeit deklariert werden und können nur beim Start der Anwendung geladen werden. Ein dynamisches Nachladen von Modulen, wie etwa bei OSGi ist nicht möglich. Die Hauptmotivation des Java-Modulsystems ist es hierbei auch gar nicht, OSGi obsolet zu machen. Stattdessen ist und bleibt der Fokus des Modulsystems die Modularisierung des JDK. Es ist auch möglich, das Java-Modulsystem zusammen mit OSGi zu verwenden. Die Kontroverse um Java Module vs. OSGi befindet sich jedoch außerhalb des Rahmens dieses Artikels und soll hier nicht weiter vertieft werden.

OSGi

Das OSGi-Framework der OSGi Alliance [OSGi] beinhaltet eine offene Plattform auf Basis des Komponentenmodells aus dem Automotive-Sektor. In OSGi können Module erstellt werden, sogenannte Bundles, die dann dynamisch bei einer sogenannten Service Registry zur Laufzeit registriert und deregistriert werden. Wie auch im Java-Modulsystem kann in den Modulen definiert werden, welche Packages und Klassen exportiert und welche importiert werden sollen.

Multi Release JARs mit dem Modulsystem

Viele Programmbibliotheken und Frameworks in Java unterstützen mehrere Java-Versionen. Das führt dazu, dass neue Sprachfeatures und neue Funktionen der Plattform-API in diesen Projekten nur zögerlich umgesetzt werden, um die Abwärtskompatibilität mit alten Java-Versionen nicht zu untergraben. Insbesondere im Hinblick auf die größeren Änderungen an der Plattform-API im Zuge von Java 9 ergibt sich das Problem, dass viele Programmbibliotheken entweder das Modulsystem von Java 9 umgesetzt haben und damit voll zu Java 9 konform sind, oder nicht. Als Resultat werden dann mehrere JAR-Artefakte für die unterschiedlichen Java-Versionen veröffentlicht. An dieser Stelle schafft das Multi-Release JAR-File in Java 9 Abhilfe.

Seit Java 9 ist es nun möglich, unterschiedliche Versionen derselben Java-Klasse für unterschiedliche Java-Versionen in der gleichen JAR-Datei vorzuhalten. Bei der Entwicklung einer Programmbibliothek können also für die Java 9-Nutzer der Bibliothek spezielle Funktionen vorgehalten werden und spezielle Sprachfeatures verwendet werden, ohne dass die Java 8-Nutzer davon etwas mitbekommen oder die Klassen auch nur sehen. Beim Ausführen des Programms werden dann ausschließlich die Klassen ausgeführt, welche für die verwendete JVM-Version vorgesehen sind. Diese Funktionalität funktioniert auch für Ressourcendateien. [MRJ, JDK9]

Um ein Multi-Release JAR als solches zu kennzeichnen, muss in der MANIFEST.MF-Datei innerhalb der JAR-Datei zunächst das Attribut Multi-Release: true gesetzt werden. Dies ist ein Hinweis an die JVM, die die JAR ausführen soll und wird von JVMs mit einer Java-Version von 8 oder niedriger einfach ignoriert.

Multi-Release JARs erstellen

Zunächst erstellen wir drei Ordner für die drei Java-Versionen (8, 9, 10), die unser kleines Testprogramm unterstützen soll. Diese Ordner nennen wir src8, src9 und src10. Unser Testprogramm besteht aus den 2 Klassen Main.java und Example.java:

public class Main {
    public static void main(String[] args) {
        new Example().doStuff();
    }
}}

Beispiel 14: Die Main-Klasse

public class Example {
    public void doStuff() {
        System.out.prinln("Beispiel für Java 8");
    }
}

Beispiel 15: Die Example-Klasse

Die Main-Klasse erzeugt dabei eine Instanz der Example-Klasse und ruft die Methode doStuff auf. Diese gibt anschließend den Text “Java 8 Example” auf der Konsole aus. Um die Snippets und insbesondere die später folgenden Konsolenbefehle kurz zu halten, werden diese beiden Klassen im Default-Package belassen. Die Main-Klasse soll für alle Versionen identisch sein, nur die Example-Klasse soll für die Java-Versionen 8, 9 und 10 unterschiedliche Implementierungen erhalten.

Die beiden oben erstellten Java-Dateien legen wir nun unter src8/ ab. Nun zur Version der Example.java für Java 9:

public class Example {
    public void doStuff() {
        System.out.prinln("Beispiel für Java 9");
    }
}

Beispiel 16: Die Example-Klasse (Java 9)

Diese sieht sehr ähnlich wie die für Java 8 aus, nur wird hier “Java 9 Example” statt “Java 8 Example” ausgegeben. Diese Datei legen wir unter src9/Example.java ab. Schließlich die Example.java für Java 10:

public class Example {
    public void doStuff() {
        System.out.prinln("Beispiel für Java 10");
    }
}

Beispiel 17: Die Example-Klasse (Java 10)

Abgelegt wird diese unter src10/Example.java. Unsere Dateistruktur sollte nun wie folgt aussehen:

./
├─ src8/
│   ├── Main.java
|   └── Example.java
├── src9/
|   └── Example.java
└── src10/
   └── Example.java

Beispiel 18: Dateistruktur

Diese Java-Dateien müssen nun alle für ihre jeweilige Version kompiliert werden. Dafür gibt es seit Java 9 das Compiler-Flag --release, welches auch gleich die korrekte Plattform-API verwendet.

Hier die Kommandozeilenbefehle, um alle Dateien mit dem Java 10 Compiler korrekt zu kompilieren:

$ javac --release 8 src8/*.java
$ javac --release 9 src9/*.java
$ javac --release 10 src10/*.java

Im nächsten Schritt werden diese nun zu einer gemeinsamen JAR-Dateien zusammengefügt. Der zugehörige Befehl ist recht lang und setzt sich aus den folgenden Segmenten zusammen:

  • jar cfe <Dateiname> <Haupteinstiegspunkt>: Das JAR-Programm aus dem JDK wird mit dem Parametern cfe (c: neues Archiv erstellen, f: Dateiname, e: Haupteinstiegspunkt) und den Parametern für Dateiname und Haupteinstiegspunkt gestartet.

  • -C <Ordner>, <Klasse>: Angabe eines weiteren Ordners mit einer Klasse, welche zum JAR-Archiv hinzugefügt werden soll.

  • -- release 9 C <Ordner>, <Klasse>: Angabe des Ordners mit der gleichen Klasse für Java-Version 9

  • -- release 10 C <Ordner>, <Klasse>: Angabe des Ordners mit der gleichen Klasse für Java-Version 10

Der vollständige Befehl für unser Beispiel lautet somit:

$ jar cfe multi.jar Main -C src8 Main.class -C src8 Example.class 
--release 9 -C src9 Example.class --release 10 -C src10 Example.class

Zunächst werden die Dateien Main.class und Example.class aus dem src8 -Ordner als Default-Klassen in die Datei multi.jar verpackt. Anschließend wird mittels der Flag --release 9 die Datei Example.class aus dem Ordner src9 für Java-Version 9 vorgemerkt. Analog dazu wird mittels --release 10 die Example.class -Datei aus dem src10 -Ordner für Java-Version 10 vorgemerkt.

Anschließend kann die Datei multi.jar mit dem Befehl java -jar multi.jar gestartet werden. Hier das Resultat für die Java 10 JVM:

Multi Release JAR ausführen unter Java-Version 10

Abbildung 2: Multi Release JAR ausführen unter Java-Version 10

Das gleiche Programm auf einer JVM mit Java 9:

Multi Release JAR ausführen unter Java-Version 9

Abbildung 3: Multi Release JAR ausführen unter Java-Version 9

Und mit Java 8:

Multi Release JAR ausführen unter Java-Version 8

Abbildung 4: Multi Release JAR ausführen unter Java-Version 8

Eine Build-Tool-Unterstützung in Maven und Gradle gibt es für den Multi-JAR-Build momentan nur durch Plugins. Wie diese im Detail funktionieren, geht allerdings bei weitem über den Fokus dieses Artikels hinaus.

Änderungen an den Standardbibliotheken

Neben dem Modulsystem gibt es noch zahlreiche weitere kleinere und größere Änderungen. Diese sind im Folgenden tabellarisch aufgelistet. Außerdem ist jeweils ein mehrseitiger Artikel zu jedem der Themen referenziert, welcher sehr viel stärker ins Detail geht.

Tabelle 1: Änderungen an den Standardbibliotheken

Neue Tools und Features

Außerdem wurden noch einige neue Tools und Features zum JDK hinzugefügt. Auch zu jedem dieser Tools und Features gibt es eine weiterführende mehrseitige Erläuterung. Hier eine kurze Liste:

Tabelle 2: Neue Tools und Features im JDK 9

Das neue Release Modell

Nachdem die Abstände zwischen den letzten Java Major-Releases vor Java 9 häufig mehrere Jahre überspannten und die Releases dann kurz vor dem Release-Termin noch einmal um Monate oder Jahre verschoben wurden, gibt es nun ein neues Release-Modell. [JUPD]

Statt dem bisherigen featurebasierten Release-Modell (Feature wird releast sobald fertig) wurde nun auf ein Release-Train-Modell gewechselt. Dieses sieht neue Major Releases in einem festen Takt alle sechs Monate vor. Wenn ein Feature bis dahin nicht fertig ist, wird es automatisch in die nächste Release-Version verschoben. [JDSK]

Der Vorteil dieses neuen Modells löst das Problem, dass ein großes Feature (wie beispielsweise das Modulsystem) den Release einer neuen Version und aller anderen für diese Version geplanten Features massiv verzögern kann, nur um dann komplett aus dem Release gestrichen zu werden. Neuerdings können fertige Features einfach mit dem nächsten Major-Release veröffentlicht werden und müssen nicht jahrelang fertig in der Release-Queue warten.

Oracle liegt damit im Trend und setzt auf ein Release-Modell, welches sich bereits im Webbrowsermarkt erfolgreich durchgesetzt hat. Kleine Bugfixes und die Korrektur von Sicherheitsproblemen wird es natürlich auch zwischen den Releases in gewöhnlichen Bugfix-Releases geben.

Da es nun also immer zwei Major-Releases pro Jahr gibt, wird nicht mehr jeder Major-Release auch gleichzeitig ein LTS-Release (Long Term Support Release) sein. Java 8 war der letzte LTS und der nächste LTS ist erst Java 11. Dadurch ist es nur begrenzt sinnvoll, im Unternehmensumfeld die Java-Version von Java 8 auf Java 9 oder 10 zu aktualisieren, da ein Update auf Java 11 mangels Long-Term-Support der Zwischenversionen direkt eingeplant werden sollte.

Der normale Support für eine Java Major-Version dauert jeweils bis zum nächsten Major-Release an. Damit ist der Support für Java 9 mit dem Release von Java 10 im März 2018 bereits eingestellt worden; Der Support für Java 10 endet im September mit dem Release von Java 11. Nach Ablauf des Supportzeitraums gibt es keine öffentlichen Updates mehr für die betroffene Java-Version. Öffentliche Updates für Java 8 wird es laut dem aktuellen Release Plan auf der Website von Oracle noch bis voraussichtlich 2020 geben. Wie lange der LTS von Java-Version 11 läuft, ist bisher nicht bekannt. [OSRM]

Ausblick: Java 10 und 11

Local Type Inference

Im März 2018 ist bereits Java 10 erschienen. Das Kernfeature von Java 10 ist ein Feature namens "Local Type Inference" [JDOC10]. Diesen Mechanismus gibt es in Java bereits bei den Lambda-Parametern und beim Diamond-Operator für generische Datencontainer.

Neu ist, dass nun mit dem Schlüsselwort var auch lokale Variablen definiert werden können. Deren Datentyp ergibt sich aus der Zuweisung eines Wertes. Beim Diamond-Operator wird der Typ des Generics über den Typ der deklarierten Variable ermittelt. In Fall von var ist es genau umgekehrt: Der Typ der Variable wird von der rechten Seite der Zuweisung abgeleitet. [RJ10]

var str = "Hello World";
var i = 5;

Beispiel 19: Deklaration einiger generischer Variablen

Der Datentyp ist zwar beliebig, aber fest. Dies bedeutet, dass er sich nach der initialen Deklaration nicht mehr ändern lässt. Anschließend an das Codebeispiel oben (Deklaration einiger generischer Variablen) könnte jetzt also beispielsweise nicht die Zeile str = 5l; folgen. Diese würde in einem Compilerfehler resultieren. Was möglich wäre, ist eine Veränderung unter Beibehaltung des Datentyps, etwa str = "Goodbye World";.

Das Ganze funktioniert auch mit Containerdatentypen:

var strings = List.of("a", "b", "c");
strings.add("d");
for (var string : strings) {
    System.out.println(string);
}

Was mit Java 11 entfernt wird

Dank des mit Java 9 eingeführten Modulsystems ist es nun weitaus einfacher für Oracle, auch größere Teile aus dem JDK und der JRE zu entfernen und separat anzubieten. Hier eine tabellarische Übersicht, welche Module mit Java 11 entfernt werden sollen:

Tabelle 3: Was mit Java 11 entfernt wird

Zusammenfassung

Das Modulsystem in Java 9 soll die Performance und die Sicherheit und Integrität von Java-Programmen verbessern. Durch die Java Module wurde quasi eine weitere Sichtbarkeitsschicht oberhalb von public hinzugefügt, mit der sich neue Projekte nun noch besser organisieren lassen. Einzig die Modulversionierung lässt zu wünschen übrig. Dafür lassen sich jetzt JAR-Dateien erstellen, die gleichzeitig mehrere Java-Versionen unterstützen können. Dadurch wird eine stärkere Codeoptimierung, insbesondere für neue Java-Versionen, ermöglicht.

Auch an den Standardbibliotheken wurde viel geändert und aktualisiert. So ist im JDK nun ein vollständiger HTTP/2-Client enthalten und die Process-API ist nun signifikant leichter zu benutzen. An vielen Stellen wurden kleinere Optimierungen vorgenommen, die das Potenzial haben, den Programmiereralltag deutlich zu vereinfachen.

Die neue JShell soll die Abwendung von Bildungseinrichtungen von Java stoppen. Das Resultat bleibt abzuwarten. Der JLinker ist ein hilfreiches Tool, mit dem sich die Stärken des Modulsystems im Hinblick auf Abwärtsskalierbarkeit und Downsizing vollständig ausspielen lassen.

Abschließend hat die Änderung des Release Modells hin zu regelmäßigen Releases und weniger LTS-Versionen signifikante Implikationen für zukünftige Java-Versionen. So enthält Java 10 außer den Local Type Inferences keine signifikant großen Features und auch die Pre-Release Versionen von Java 11 enthalten momentan kaum mehr als die Entfernung von JavaFX und der Java-Enterprise Module bereit.

Bibliographie

[HTTP] HTTP/2 Client – Java 9
(https://blog.oio.de/2016/08/24/http2-client-java-9/)
Jacobs, Steffen, Orientation in Objects GmbH; 2016-08-24

[PROC] Process API – Java 9
(https://blog.oio.de/2016/09/02/process-api-java-9/)
Jacobs, Steffen, Orientation in Objects GmbH; 2016-09-02

[HTTP2] Hypertext Transfer Protocol Version 2 (HTTP/2)
(https://tools.ietf.org/html/rfc7540)
Belshe, M., Bitgo; Peon, R., Google, Inc; Thomson, M. Ed., Mozilla; 2015-05-01

[FLOW] Publish-Subscribe mit der Flow-API in Java 9
(https://blog.oio.de/2018/05/04/publish-subscribe-mit-der-flow-api-in-java-9/)
Jacobs, Steffen, Orientation in Objects GmbH; 2018-05-04

[JDOC9] Java® Platform, Standard Edition and Java Development Kit Version 9 API Specification
(https://docs.oracle.com/javase/9/docs/api/overview-summary.html)
Oracle, , Oracle, Inc.; 2017-10-17

[COLL] Collections – Factory Methoden
(https://blog.oio.de/2018/05/09/collections-factory-methoden/)
Jacobs, Steffen, Orientation in Objects GmbH; 2018-05-09

[COMP] Completable Futures: Java 9 Update
(https://blog.oio.de/2018/05/07/completable-futures-java-9-update/)
Jacobs, Steffen, Orientation in Objects GmbH; 2018-05-07

[SAFE] SafeVarargs-Annotation in Java
(https://blog.oio.de/2018/05/03/safevarargs-annotation-in-java/)
Jacobs, Steffen, Orientation in Objects GmbH; 2018-05-03

[STRM] Streams – Neue Methoden in Java 9
(https://blog.oio.de/2018/05/15/streams-neue-methoden-in-java-9/)
Jacobs, Steffen, Orientation in Objects GmbH; 2018-05-15

[JSHL] JShell
(https://blog.oio.de/2016/08/25/jshell/)
Jacobs, Steffen, Orientation in Objects GmbH; 2016-08-25

[JLNK] Individuelle JRE-Images mit dem JLinker erstellen
(https://blog.oio.de/2018/05/14/individuelle-jre-images-mit-dem-jlinker-erstellen/)
Jacobs, Steffen, Orientation in Objects GmbH; 2018-05-14

[JOLD] Java für alte Plattformen kompilieren
(https://blog.oio.de/2018/04/25/java-fur-alte-plattformen-kompilieren/)
Jacobs, Steffen, Orientation in Objects GmbH; 2018-04-25

[JGC1] The G1 Garbage Collector
(https://blog.oio.de/2016/08/30/the-g1-garbage-collector/)
Jacobs, Steffen, Orientation in Objects GmbH; 2016-08-30

[JFX] Java FX
(http://www.oracle.com/technetwork/java/javafx/overview/index.html)
Oracle, , Oracle, Inc.; 2012-08-15

[OJFX] OpenJDK Java FX
(https://github.com/javafxports/openjdk-jfx)
OpenJDK, , OpenJDK; 2011-11-06

[JFXR] The Future of JavaFX and Other Java Client Roadmap Updates
(https://blogs.oracle.com/java-platform-group/the-future-of-javafx-and-other-java-client-roadmap-updates)
Donald Smith, , Oracle; 2018-03-07

[JXWS] JSR 224: JavaTM API for XML-Based Web Services (JAX-WS) 2.0
(https://jcp.org/en/jsr/detail?id=224)
JCP, , Java; 2006-05-11

[JAXB] JSR 222: JavaTM Architecture for XML Binding (JAXB) 2.0
(https://jcp.org/en/jsr/detail?id=222)
JCP, , Java; 2006-05-11

[JABE] JSR 925: JavaBeansTM Activation Framework 1.1
(https://jcp.org/en/jsr/detail?id=925)
JCP, , Java; 2006-05-11

[CMAN] JSR 250: Common Annotations for the JavaTM Platform
(https://jcp.org/en/jsr/detail?id=250)
JCP, , Java; 2006-05-11

[CORB] Common Object Request Broker Architecture: Core Specification
(www.omg.org/cgi-bin/doc?formal/04-03-12.pdf)
OMG, , Object Management Group, Inc.; 2012-03-04

[JTA] JSR 907: JavaTM Transaction API (JTA)
(https://jcp.org/en/jsr/detail?id=907)
JCP, , Java; 2002-11-06

[JDK9] OpenJDK Java 9 Release Notes
(http://openjdk.java.net/projects/jdk9/)
OpenJDK, , OpenJDK; 2017-09-21

[OSRM] Oracle Java SE Support Roadmap
(http://www.oracle.com/technetwork/java/javase/eol-135779.html)
Oracle, , Oracle, Inc.; 2018-03-05

[JUPD] JDK Updates nach Java 9 – Oracle stellt neues Release-Train-Modell in Aussicht
(https://blog.oio.de/2017/09/08/jdk-updates-nach-java-9-oracle-stellt-neues-release-train-modell-in-aussicht/)
Maier, Thorsten, Orientation in Objects GmbH; 2017-09-08

[JDSK] There’s not a moment to lose!
(https://mreinhold.org/blog/forward-faster)
Reinhold, Mark, ; 2017-09-06

[JDOC10] Java® Platform, Standard Edition and Java Development Kit Version 10 API Specification
(https://docs.oracle.com/javase/10/docs/api/overview-summary.html)
Oracle, , Oracle, Inc.; 2018-03-20

[MRJ] JEP 238: Multi-Release JAR Files
(http://openjdk.java.net/jeps/238)
Sandoz, Paul, ; 2014-06-08

[OSGI] The Dynamic Module System for Java
(https://www.osgi.org/)
OSGi, , OSGi Alliance; 2018-05-22

[JGSW] Jigsaw – Java 9
(https://blog.oio.de/2016/08/26/jigsaw-java-9/)
Jacobs, Steffen, Orientation in Objects GmbH; 2016-08-26

[MCRS] Microservices - a definition of this new architectural term
(https://martinfowler.com/articles/microservices.html)
Fowler, Martin, ; Lewis, James, ThoughtWorks; 2014-03-25

[JMOD] JEP 200: The Modular JDK
(https://blog.oio.de/2016/08/26/jigsaw-java-9/)
Reinhold, Mark, ; 2014-07-22

[JGSWO] Project Jigsaw
(http://openjdk.java.net/projects/jigsaw/)
OpenJDK, , ; 2014-07-22

[JGSWQS] Project Jigsaw: Module System Quick-Start Guide
(http://openjdk.java.net/projects/jigsaw/quick-start)
OpenJDK, , ; 2014-07-22

[REFL] The Java™ Tutorials - Trail: The Reflection API
(https://docs.oracle.com/javase/tutorial/reflect/)
Oracle, , Oracle, Inc.; 2014-07-22

[SMOD] The State of the Module System
(http://openjdk.java.net/projects/jigsaw/spec/sotms/#the-module-path)
Mark, Reinhold, Oracle, Inc.; 2018-03-08

Zum Geschaeftsbreich Competence Center

Service

Competence Center

Veröffentlichungen

Artikelübersicht