16.03.2021

Java Releases in 2021 - eine Einschätzung

Prozesse > Java Releases in 2021 - eine Einschätzung

Das Jahr 2021 ist nun fast schon im 2. Quartal angekommen und es wird daher Zeit einen kleinen Rückblick auf die Entwicklungen dieses Jahres, sowie einen Ausblick auf die noch anstehenden Neuerungen zu geben. Mit der Erhöhung der Releasekadenz von neuen Versionen im Jahr 2017 hat das Tempo in der Weiterentwicklung von Java merklich angezogen, daher ist es manchmal schwer den Überblick über alle Entwicklungen zu behalten, und es lohnt sich manchmal, ein wenig zu pausieren und sich die Vielzahl an Projekten und kleinen und großen Verbesserungen in Ruhe anzuschauen. Mit dieser Serie an Blog Artikeln werden wir genau dies tun. In diesem ersten Teil wird es um die neuen Java Releases gehen.

Für das Jahr 2021 stehen wie gewohnt 2 neue Java SE Versionen im Abstand von circa 6 Monaten an. Die erste - Java 16 - ist als generelle Veröffentlichung für den 16. März geplant und damit zum Veröffentlichungszeitpunkt dieses Artikels wahrscheinlich grade eben erschienen.

Die zweite - Java 17 - ist eine besondere Version: Es ist seit der Umstellung auf den schnelleren Veröffentlichungszyklus die zweite Long-Term-Support (LTS) Version, welche mit einem besonderen Augenmerk auf Stabilität und einem weitaus längerem Support-Zeitraum aufwarten kann. Grade für Unternehmen, die auf der Suche nach Stabilität und Verlässlichkeit sind, wird diese Version, deren generelle Verfügbarkeit für den September 2021 angesetzt ist, von großer Bedeutung sein.

Schauen wir uns die Neuerungen der zwei neuen Java Versionen mal im Detail an.

Java 16

Die Version 16 fühlt sich für den geneigten Java-Entwickler wie Weihnachten und Ostern an einem Tag an. Wir durften in Version 14 und 15 schon mit einigen schönen neuen Features wie die heiß erwarteten Records und das prägnante Pattern-Matching mit instanceof spielen; allerdings nur mit dem --enable-preview Flag, welches auf der Arbeit natürlich aus bleiben musste. Nun sind sie hier: Records und Pattern-matching für instanceof werden in Java 16 endgültig veröffentlicht und es kann endlich refactored werden.

Records

Viele Entwickler kennen es: Man braucht eigentlich nur eine Klasse die 2 Objekte hält, mehr nicht. So einfach ist es in Java aber nun mal nicht. Was ist mit einer sinnvollen equals() Definition? Wie soll toString() aussehen? Wie genau schreibt man noch eine stabile Implementation von hashCode()? Ach ja, und da sind ja noch die geter und seter. Java hat nicht umsonst den Ruf ausschweifend zu sein. Libraries wie Lombok oder automatische Generierung durch IDEs helfen hier zwar ein wenig, aber beide Aushilfen bringen ihre eigenen Nachteile mit.

Genau diese Probleme adressieren die Records. Ein Record ist eine unveränderliche (immutable) Datenklasse. Sie würde als Beispielimplementation für einen Punkt mit zwei Koordinaten x und y so aussehen:

1public record Point (int x, int y) {
2
3}

Der Compiler kümmert sich hier um sinnvolle Implementierung von equals(), toString() und hashCode() und einen Standardkonstruktor der alle Eigenschaften instanziiert. Auch automatische Zugriffsmethoden in Form von x() und y() werden implementiert. Obacht ist hier geboten, da diese Zugriffsmethoden nicht der standardisierten Form von Gettern und Settern von Beans folgen. Dies ist beabsichtigt, da sie nicht als Ersatz für (normalerweise veränderliche) Beans dienen sollen, sondern eher als Transportklassen für gruppierte Daten (Tupel). In dem Licht erscheint die Entscheidung auch sinnvoll.

Records haben auch noch einige Einschränkungen: Sie sind implizit final, können also nicht abgeleitet werden, und können auch nicht von anderen Klassen ableiten oder abstract sein. Abgesehen davon verhalten sie sich aber wie normale Klassen, sie können also Interfaces implementieren, generisch sein, instanziiert werden oder einen Konstruktor definieren der extra Verhalten hinzufügt wie Validierung etc.

Somit dürfen Records in den meisten Codebasen schnell Einzug halten und dort für eine Reduktion von Bugs in Bezug auf fehlerhafte Implementierungen der Standardmethoden sorgen oder sogar merkliche Performanceverbesserungen bringen.

instanceof Pattern-Matching

Wer objektorientierten Code mit ordentlicher Kapselung schreibt, wird um folgendes Pattern nicht herumkommen: Man prüft eine Oberklasse auf spezielle Implementierungen und will das Verhalten auf Basis des speziellen Typs verändern. Dies ist in vergangenen Java Version immer folgendermaßen gelöst worden:

1if (obj instanceof Foo) {
2    var foo = (Foo)obj;
3}

Den Cast jedes Mal auszutippen ist allerdings zeitintensiv und fehleranfällig. Sprachen mit sogenannten Type Narrowing wie Kotlin oder Typescript vermeiden das Problem geschickt: Der Compiler weiß einfach ab dem Sprung in den if-Block, dass obj vom Typ Foo ist, und bietet entsprechen den Zugriff auf die Methoden und Eigenschaften der Klasse Foo an.

In Java wird das Problem etwas anders gelöst. Wir verlagern hier den Cast in eine implizite Variablendeklaration in die if-Kondition. Das sieht in der Praxis dann so aus:

1if (obj instanceof Foo foo) {
2    // Zugriff auf foo ist hier verfügbar!
3}

Weniger Zeilen zu tippen und damit weniger Möglichkeit Fehler zu machen. Alle sind zufrieden.

Compilerwarnungen zu verpackten Primitiven

Java hat seit einiger Zeit das Konzept der verpackten Primitive. Dies sind primitive Typen, wie int, die in einer "Verpackung" in Form eines Objekts mit Referenzierung stecken. Vorteil dieser Typen ist zum einen, dass sie den Wert null annehmen können, zum anderen hat man so die Möglichkeit sie in generischen Typenparametern zu verwenden, was als Primitiv nicht möglich ist.

Durch Weiterentwicklungen bei Project Valhalla (näheres dazu im nächsten Teil der Serie), sollen diese verpackten Varianten von Primitiven bald zu Value Objects werden, was im Kontext von Valhalla zwar sehr viel Sinn ergibt, aber einige Einschränkungen mit sich bringt.

So kann über Value Objects z.B. nicht Multithreading synchronisiert werden, weil sie die nötigen Eigenschaften dafür nicht sich führen wie normale Referenzobjekte. Die Details hierzu sind hier nachzulesen, es sei aber erwähnt, dass die meisten Einschränkungen schon länger als schlechter Stil galten und auch relativ einfach zu Umschiffen sind, damit also kein größeres Problem darstellen sollten.

Alpine Linux Unterstützung

Für Anwendungen die im Cloudumfeld deployed werden ist dieses Feature besonders interessant: Das JDK unterstützt ab Java 16 nativ die abgespeckte Linux Distribution Alpine Linux. Mit 6 MB ist Alpine ein echtes Leichtgewicht unter den Linux Distributionen, aber diese Leichtigkeit kommt natürlich nicht ohne Einschränkungen. So setzt Alpine auf musl, eine Implementation der C Standardbibliothek die auf statisches Linken ausgelegt ist. Musl muss daher explizit unterstützt werden, was Java 16 nun auch tut.

Unternehmen die ihre Container in der Cloud deployen wird dies freuen: Sie können die beliebte Alpine Linux Distribution nun auch für ihre Java Deployments als Basisimage ohne größere Anpassungen nutzen was die Imagegrößen drastisch reduziert und somit Zeit als auch Geld spart.

Unix Domain Sockets

TCP/IP Sockets sollte jeder Entwickler kennen. Sie sind der Standardweg wie Prozesskommunikation innerhalb eines Hosts in den meisten Betriebssystemen funktioniert, und diesen Job erledigen sie auch gut. Auf Unix-ähnlichen Betriebssystemen gibt es noch eine weitere Möglichkeit Sockets zu erstellen und darüber mit Prozessen zu kommunizieren: Unix-domain socket channels. Sie sind den normalen TCP/IP Sockets ziemlich ähnlich, vermeiden aber viel Overhead der normalen TCP/IP Protokolle, was zu einer merklichen Leistungssteigerung führen kann, grade in Bezug auf Latenz. Java 16 führt nun eine native Unterstützung dieser (wie für Unix typisch) dateibasierten Sockets ein.

Es wird sich noch zeigen, welche Bibliotheken eine Unterstützung zeitnah anbieten werden. Gunnar Morling hat hier eine kleine Bestandsaufnahme der derzeitigen Situation gemacht, die wirklich lesenswert ist, aber auch einige Ernüchterungen bereithält. Wie sich die Performance Gewinne für Low-Latency Anwendungen ausgestalten, werden wir also erst sehen, wenn populäre Frameworks und Bibliotheken eine native Unterstützung anbieten und somit ohne größere Probleme auf Produktionlasten losgelassen werden können.

Weitere Neuerungen

Abgesehen von den "spannenden" Features hat sich auch unter der Haube im JDK einiges getan. Vor allem Verbesserungen in Bezug auf das gleichlaufenden Verarbeiten des Thread-Stacks im noch experimentellen ZGC Garbage Collector, was GC Pausen reduzieren soll, sowie eine Anpassung die Metaspace Speicher (der Speicher, der generelle Informationen über Klassen bereitstellt) schneller an das Betriebssystem freigeben lassen aufhorchen.

Im Bereich Hosting des Codes und C++ Version der Implementation des JDKs hat sich auch einiges getan. Diese Änderungen sind aber eher uninteressant für den normalsterblichen Java Nutzer, wer trotzdem mehr erfahren will kann sich hier einen Überblick verschaffen.

Java 17

Da Java 17 erst im Herbst erscheinen wird, ist der genaue Umfang des insgesamt dritten LTS Releases des JDK Projekts noch etwas nebulös. Fest stehen bisher zwei Features, die bisher zum Release im September 2021 eingereicht worden sind. An sich kann die Bedeutung der Veröffentlichung aber nicht genug betont werden - der Supportzeitraum wird wahrscheinlich bis ins Jahr 2030 hineinreichen, was für Unternehmen ein wichtiger Evaluationspunkt sein wird. Es ist daher zu erwarten, dass viele Java Anwendungen noch lange auf dieser Version laufen werden, daher sollte dieser Release besonders gut verfolgt werden.

Rendering mit der Metal API unter MacOS

Seitdem Apple Metal in 2015 als neue Standard-API für Rendering von 2D und 3D Szenen unter MacOS bereitgestellt hat, ist klar gewesen, dass die ohnehin vernachlässigte OpenGL API Apple ein Dorn im Auge ist und wahrscheinlich bald nicht mehr unterstützt wird. Seit MacOS Mojave ist dieser Fall auch eingetreten: OpenGL wird in MacOS nicht mehr weiterentwickelt und erhält keine Featureupdates mehr.

Da Java für seine 2D APIs in MacOS OpenGL unter der Haube nutzt, ist dies natürlich ein Problem. Dieses Problem wird mit dem JEP 382 nun gelöst. Java bekommt eine neue Metal basierte Pipeline spendiert, welche das JDK fit für zukünftige Releases von MacOS machen soll.

Für Entwickler ändert sich hier eigentlich nichts, aber es dürften einige Performance Gewinne erwartet werden durch die Metal API, welche mehr hardwarenahen Zugriff bietet und somit weniger Overhead als OpenGL hat.

Vereinheitlichung der PRNG Schnittstellen

Pseudozufallszahlengeneratoren haben im JDK schon immer existiert, Klassen wie Random, ThreadLocalRandom oder SplittableRandom sollten Entwicklern bekannt sein. Auffallend ist aber, dass diese Klasse zwar weitestgehend gleiche Methoden haben, aber kein gemeinsames Interface implementieren, was den Austausch der Implementierungen für Anwendungen schwierig macht.

Mit Java 17 soll nun ein genau solches Interface - RandomGenerator - kommen, mit einigen Spezialisierungen für PRNGs, die besondere Eigenschaften besitzen (wie z.B. Überspringbarkeit). Abgesehen davon sollen einige weitere Verbesserungen der Klassen kommen die den Speicherverbrauch verbessern sollen sowie zukünftige Implementierungen von neuen Algorithmen für das Erzeugen von Zufallszahlen erleichtern sollen.

Fazit

2021 wartet mit einigen interessanten und lang erwarteten Neuerungen für Java auf. Java 16 hat bereits die Messlatte hoch angelegt, Java 17 wird hoffentlich noch zeigen, dass selbst LTS Releases nicht an neuen Features sparen.

Im nächsten Teil des Technologieausblicks werden wir uns generellen Trends wie GraalVM, GraphQL und Serverless zuwenden.

Sie möchten wissen, ob Java die richtige Technologie für Ihr Business ist? Dann vereinbaren Sie gerne ein unverbindliches Erstgespräch mit uns!

Unsere Leistungen

Standort Hannover

newcubator GmbH
Bödekerstraße 22
30161 Hannover

Standort Dortmund

newcubator GmbH
Westenhellweg 85-89
44137 Dortmund