jdk - JAX https://jax.de/tag/jdk/ Java, Architecture & Software Innovation Fri, 18 Oct 2024 12:52:45 +0000 de-DE hourly 1 https://wordpress.org/?v=6.5.2 Wo steht Java? https://jax.de/blog/wo-steht-java-nachrichten-aus-einem-bewegten-oekosystem/ Tue, 11 Jul 2023 10:17:01 +0000 https://jax.de/?p=88795 Der „2023 State of the Java Ecosystem“-Report von New Relic zeigt interessante Entwicklungen im Java-Umfeld: So hat zum Beispiel Oracle seine Marktdominanz in den letzten Jahren verspielt, Container gehören unter Java zum Standard und Java 17 stellt – gemessen an seiner Beliebtheit – alle in den Schatten. Ein Überblick über den aktuellen Stand und wichtige Ereignisse in der Geschichte von Java.

The post Wo steht Java? appeared first on JAX.

]]>
Sun Microsystems veröffentlichte Java im Jahr 1996, um eine portablere und interaktivere Methode zur Entwicklung moderner Multimediaanwendungen zu bieten. Es entstand die erste plattformunabhängige, objektorientierte Programmiersprache. Bis heute ist sie überaus populär: Java wird in fast allen wichtigen Industrie- und Wirtschaftszweigen eingesetzt, bietet Tausende von Bibliotheken und wird gut unterstützt. Sicherheitsaspekte und Parallelverarbeitung (Threads) spielten von Beginn an eine wichtige Rolle.

Ab 2006 veröffentlichte Sun Teile von Java, wie etwa den Compiler javac und die Hotspot Virtual Machine, sowie später große Teile des Java-SE-Quellcodes zur Erstellung eines JDK (Java Developer Kits) unter der GPU General Public License – ein wichtiger Impuls für die Open-Source-Bewegung. Zugleich rief das Unternehmen die OpenJDK-Community ins Leben, die auf der offiziellen, frei verfügbaren Implementierung der Java-Plattform beruht. Mit der Übernahme von Sun durch Oracle im Jahr 2010 änderte sich zunächst nichts an der Open-Source-Lizenzierung. Oracle investierte weiterhin in die Programmiersprache und in ein umfassendes Ökosystem.

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

Oracles Marktdominanz ist Geschichte

Indes lassen sich interessante – Oracle-Kritiker:innen nennen es vorhersehbare – Entwicklungen am Markt beobachten. Schon in dem „State of the Java Ecosystem“-Report aus dem Jahr 2020 [1] konstatierte Herausgeber New Relic einen Rückgang von Oracles Dominanz auf dem Java-Markt. New Relic wertet für die Studie Daten von Entwickler:innen aus, die die Observability-Plattform des Unternehmens nutzen, um ihre Softwareanwendungen zu monitoren und zu debuggen. Anonymisierte und verallgemeinerte Daten mehrerer Millionen VMs helfen so, einen Überblick über die Entwicklungen des Java Ökosystems zu erhalten.

Demnach verfügte Oracle im Jahr 2020 über einen Marktanteil von etwa 75 Prozent, Tendenz bereits sinkend. Als zweitbeliebtester Anbieter hatte sich das von der Community geführte AdoptOpenJDK etabliert. Auffällig dabei: Während zu dem Zeitpunkt erst etwa elf Prozent der Nutzer:innen auf die damals neue Version Java 11 migriert waren, liefen bereits gut ein Drittel der AdoptOpenJDK VMs unter Java 11. Das lässt den Schluss zu, dass Anwender:innen die Migration nutzten, um zugleich den Distributor zu wechseln. Der wahrscheinlichste Grund: Oracle hatte eine neue Lizenzierungspolitik eingeführt. Zwar blieb die im Januar 2019 veröffentlichte Oracle Java 8 SE kostenfrei. Für den Support und die Nutzung von Updates sowie Security Patches benötigten kommerzielle Anwender:innen allerdings nun eine kostenpflichtige Lizenz.

Die Kund:innen reagierten verärgert. Dass viele sich gegen ein Lizenzabonnement und für eine Alternative entschieden, wird besonders im aktuellen „State of the Java Ecosystem“-Report [2] deutlich. So sank der Marktanteil von Oracle bis zum Jahr 2022 auf 34 Prozent, aktuell beziehen nur noch etwa 28 Prozent ihr JDK von dem einstigen Marktführer. Diese Position hat nun Amazon mit einem Anteil von etwa 31 Prozent übernommen. Sicher profitiert Amazon dabei von dem seit vielen Jahren stetig wachsenden AWS-Geschäft und der großen Community drumherum. Java Coretto ist eine plattformübergreifende Distribution, die auf OpenJDK basiert und damit auch auf dessen Lizenzierungsrichtlinien (GNU General Public License). Die Auslieferung der neuen Releases geht dabei mit den LTS-Releases von OpenJDK Hand in Hand. Die vierteljährlichen Updates enthalten Bugfixes und Performanceoptimierungen.

Inzwischen hat Oracle seine Lizenzpolitik modifiziert: Seit Java 17, dem bisher letzten LTS-Release, kehrte der Anbieter (beinahe) zu seinem kostenlosen Angebot zurück. Nutzer:innen müssen nun den „No Fee Terms and Conditions“ (NFTC) zustimmen und erhalten dafür kostenlose Updates bis Herbst 2024. Das offizielle Supportende ist allerdings für 2026 terminiert. Wer auch nach 2024 von kostenlosen Updates und Patches profitieren möchte, muss entweder für den Support zahlen oder zügig auf die im Herbst 2023 erscheinende Java-Version 21 LTS migrieren. Nicht nur Amazon weiß indes den Oracle-Lizenz-Wirrwarr für sich zu nutzen. So bauten beispielsweise auch die JDK-Distributoren Eclipse Adoptium und RedHat ihre Marktanteile aus (auf mehr als zwölf und 10,5 Prozent) und sind damit keineswegs nur Nischenanbieter.

LTS für die Produktion, Nicht-LTS zum Experimentieren

Der Wechsel zu einem anderen Distributor steht vor allem dann im Raum, wenn ohnehin auf eine neue Java-Version migriert werden muss oder soll. Seit der Übernahme von Sun Microsystems durch Oracle ließen die neuen Releases zunächst jeweils mehr als drei Jahre auf sich warten. Zwischen dem Erscheinen der Version 8, die, wenn auch nicht offiziell so bezeichnet, als Long-Term-Support-Version gelten kann, und dem Release 11 war lange nicht klar, wann das nächste Java LTS erscheinen würde. Nach Java 9 switchte Oracle seinen Releasezyklus und veröffentlichte fortan halbjährlich neue Major Releases. Was mehr Übersicht und Planungssicherheit bringen sollte, stieß auf ein geteiltes Echo. Befürworter:innen freuten sich über kürzere Wartezeiten auf neue Features und Bugfixes. Kritiker bemängelten die kurzen Supportzeiten der Nicht-LTS-Versionen und zweifelten, ob das ganze Umfeld aus IDEs und Konnektoren hier mithalten könne.

Folglich werden vor allem LTS-Versionen (Java 8, Java 11 und Java 17) in der operativen Anwendungsentwicklung eingesetzt. Die Nicht-LTS-Versionen eignen sich hingegen, um zwischendurch neue Features zu testen und die nächste LTS-Migration zu planen. Diese verständliche Zurückhaltung in produktiven Systemen erklärt, warum noch im Jahr 2020 fast 85 Prozent der Java VMs auf Java 8 liefen (laut der Datenerhebung von New Relic), obwohl Java 11 LTS bereits zwei Jahre erhältlich war. Erst etwa elf Prozent nutzen zu diesem Zeitpunkt bereits Java 11.

SIE LIEBEN JAVA?

Den Core-Java-Track entdecken

 

Dennoch warteten viele Development-Teams nicht bis zum Launch der aktuellen Java-LTS-Version 17, sondern migrierten ihre VMs auf Java 11: Aktuell (im Jahr 2023) laufen mehr als 56 Prozent der Java VMs unter Java 11, knapp ein Drittel ist bisher noch auf Java 8 verblieben. Java 17 scheint sich aber nun dennoch durchzusetzen: Während es mehrere Jahre gedauert hat, bis Java 11 eine entsprechende Akzeptanz erreichte, vervielfachte Java 17 seinen Anteil innerhalb eines Jahres. Basierten im vergangenen Jahr nur etwa ein Prozent der von New Relic ausgewerteten Installationen auf Java 17, waren es im Frühling dieses Jahres bereits neun Prozent. Die Nicht-LTS-Versionen spielen indes die erwähnte Nebenrolle: Die beliebtesten unter ihnen sind Java 14 (knapp 0,6 Prozent Anteil) und Java 15 (gut 0,4 Prozent). Noch weniger Installationen verzeichnen die Versionen 16, 18 und 19.

Entwicklung und Migration: von Frameworks und Open-Source-Managern

Ist die Migration auf eine neue Java-Version aufwendig? Auf diese Frage gibt es keine einfache Antwort, denn viele Faktoren spielen eine Rolle. Grundsätzlich ist hierbei die Plattformunabhängigkeit von Java ein großer Vorteil. Erscheint eine neue Version, werden die IDEs (Integrated Development Environments) zügig angepasst. So werden zum Beispiel bei den meisten Distributionen mit der neuen Version die modifizierten Compiler etwa für die verschiedenen Gerätebetriebssysteme ausgeliefert, sodass die Anwendungen auch mit der neuen Java-Version auf den bisherigen Endgeräten laufen. Dennoch reicht eine einfache Neukompilierung der Anwendungen normalerweise nicht aus. Oft muss der Code angepasst werden, vor allem dann, wenn Drittanwendungen oder Funktionen benutzt werden.

Zudem ist es nicht ganz einfach, die verschiedenen genutzten Frameworks und Java Services im Auge zu behalten. Frameworks haben sich aus den Communities heraus entwickelt – aus Ansammlungen vorgefertigter Codeabschnitte sind ganze Java-Plattformen mit Library-Sammlungen von vordefinierten Klassen und Funktionen geworden –, Entwickler:innen haben die Qual der Wahl. Marktführend ist hier das Spring Boot Framework von VMware, das auf dem vor gut 20 Jahren entwickelten Open Source Framework Spring beruht. Spring Boot liegt das Designprinzip „Konvention vor Konfiguration“ zugrunde. Das bedeutet, dass vorgefertigte Funktionen bereits so vorkonfiguriert sind, dass eigene lauffähige Anwendungen recht einfach entwickelt werden können. So vereinfacht das Framework nicht nur die Entwicklung von Microservices, sondern auch komplexerer Softwaremodule.

Im Zuge technologischer Trends wie Containerisierung und Cloud Computing sind in den letzten Jahren Frameworks entstanden, die mit erweiterten Basiskonzepten arbeiten. SpringBoot gilt als reaktives Framework – auch wenn die Definitionen, was genau unter Reaktivität zu verstehen ist, teils sehr weit auseinandergehen. Gemeinhin meint reaktiv, dass asynchrone Datenströme die Programmierung bestimmen und die Anwendung schließlich auf Ereignisse im Datenstrom reagiert. Frameworks, wie zum Beispiel Quarkus, erfreuen sich wachsender Beliebtheit: Das containernative Framework, das für die Ausführung in einer Kubernetes-Umgebung konzipiert ist, kombiniert imperative (also klassische, ablauforientierte) und reaktive Programmierprinzipien. Der Fokus liegt auf leistungsbezogenen Funktionen, was es interessant für die Entwicklung von Microservices und Cloud-Infrastrukturen macht.

Viele Developer:innen nutzen gleich mehrere Frameworks, um bei der Erstellung der eigenen Anwendungen Zeit zu sparen. Das ist nicht ganz unkompliziert, haben doch vor allem größere Frameworks definierte Regeln und interne Abhängigkeiten. Letztere fließen in den eigenen Java-Code ein, Codeabschnitte verschiedener Frameworks sollten nicht durcheinandergeraten, und die Codes und Bibliotheken müssen auf allen Plattformen gepflegt werden. In manchen, vor allem größeren Unternehmen findet man inzwischen einen dedizierten Open-Source-Manager, der sich um diese Fragen kümmert.

Anwendungen im Containerformat

Die Entwicklungen in Java spiegeln auch die allgemeinen Development-Trends wider. Bereits Java 9 führte einige Funktionen ein, um die Arbeit mit Containern zu verbessern. Werden Anwendungen mit Hilfe von Containertechnologien entwickelt, entsteht eine Kapsel, die alle notwendigen Services für den Betrieb der Anwendung enthält. Damit werden Anwendungen unabhängiger von Betriebssystemen oder Infrastrukturen ausführbar. Liegen dem Betrieb Plattformen wie Kubernetes oder Amazon ECS zugrunde, sind sowieso Container-Images notwendig. Heute wird die Mehrzahl der Java-Anwendungen – etwa 70 Prozent – in Form von Container-Images ausgeliefert.

DIE KUNST DER SOTWARE-ARCHITEKTUR

Architecture & Design-Track entdecken

 

Eine typische Funktion, welche die Arbeit mit Containern erleichtert, ist die Möglichkeit, die Startflag -XX:MaxRAMPercentage zu setzen. Sie ersetzt die genaue Definition einer maximalen Heap-Größe, also der Speicherkapazität, die Java für die Anwendungsausführung reservieren soll. Eigentlich ist die Java Virtual Machine (JVM) als Teil der Java-Runtime-Umgebung dafür zuständig, dass eine Java-Applikation portabel ist und ihr genügend Speicher zur Verfügung steht. Weil Anwendungen immer um begrenzte Ressourcen konkurrieren, prüft die JVM zunächst, welche Ressourcen zur Verfügung stehen, und konfiguriert dann die Java-Anwendungen möglichst optimal. Diese sogenannten Ergonomics sind automatisierte Standards, die auch manuell eingestellt werden können.

Doch containerbasierte Anwendungen funktionieren anders: Es sind kleinere, gekapselte Applikationen, denen alle Ressourcen innerhalb des Containers zur Verfügung stehen. Standardeinstellungen wie etwa die Heap-Größe, die die JVM wählt, um Ressourcen zu schonen und zu teilen, erweisen sich nun als unpassend. Häufig bleiben dabei sogar Ressourcen ungenutzt, weil die JVM beispielsweise nur einen Teil des Heap-Speichers zuweist, obwohl innerhalb des Containers ein wesentlich größerer Anteil sinnvoll wäre. Die Startflag löst das Problem, indem sich nun ein prozentualer Anteil an Speicher zuweisen lässt. Das ist deutlich flexibler und nutzt den vorhandenen Speicher containeroptimiert besser aus.

Immer mehr Entwickler:innen nutzen zudem Multi-Core-Konfigurationen, auch dann, wenn sie containerbasiert entwickeln. Noch läuft mehr als ein Drittel und damit die Mehrheit der Java-Anwendungen mit nur einem Core. Doch die Tendenz ist rückläufig – vor einem Jahr waren es noch mehr als 40 Prozent. Fast genauso viele Anwendungen (knapp 30 Prozent) werden inzwischen entwickelt, um auf acht Cores optimale Performance zu erzielen – ein Anstieg um fast 50 Prozent. Die Arbeit mit Threads – und damit auch das Multithreading, also das parallele Abarbeiten von Prozessen – gehörte von Beginn an zu den überzeugenden Argumenten von Java als Programmiersprache. Doch erst seit Java 8 waren die Fortschritte in diesem Bereich so maßgeblich, dass die Entwicklung von Anwendungen für Multi-Core-Umgebungen praktikabel wurde. Bei nicht containerbasierten Java-Anwendungen ist diese Praktik entsprechend weiter verbreitet.

Garbage Collectoren: von Parallel bis G1

Denkt man über das Thema Speicheroptimierung nach, ist auch das Thema Garbage Collector (GC) von entscheidender Bedeutung. Genau wie die Heap-Konfiguration gehört die Wahl des GC zu den Ergonomics der JVM. Die automatische Garbage Collection ist ein Prozess, der den Heap-Speicher auf nicht verwendete Objekte untersucht, die dann gelöscht werden. So wird zwar wieder Speicher für die Anwendung frei, allerdings verbraucht auch der Prozess selbst Ressourcen – und zwar nicht zu knapp. Die Wahl des richtigen Algorithmus ist deshalb wichtig.

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

In den Anfangszeiten von Java gab es zunächst nur einen ParallelGC: Die Java-Anwendung musste gestoppt werden, damit der GC die unnützen Objekte ausfindig machen konnte. Alle Anfragen, die während dieser Unterbrechung an die Anwendung gestellt wurden, mussten warten, bis der GC seine Arbeit beendet hatte. Je nach definierter Heap-Größe variierte die Dauer der Unterbrechung. Bis Java 8 war der ParallelGC die Standardform der Müllbeseitigung. Der ParallelGC nutzt mehrere Threads gleichzeitig, was wiederum Overhead erzeugt. Das Pendant, der SerialGC, arbeitet im Prinzip gleich, nutzt aber nur einen Thread. Für clientartige Single-Prozessor-Umgebungen eignet er sich daher besser.

Mit der Java Version 1.2 kam ein GC hinzu, der ein anderes Prinzip nutzt: der Concurrent Mark Sweep GC, kurz CMS. Er arbeitet, zumindest teilweise, „concurrent“ (also gleichzeitig) zur Anwendung. Das hat Vor- und Nachteile: Zum einen werden die notwendigen Pausen deutlich kürzer, zum anderen wird aber die Anwendung langsamer, während der CMS arbeitet. Wahrscheinlich war das der Grund, warum sich nie eine Mehrheit für den CMS entschied, der in Java 8 in seiner letzten Version erschien. Mehr als ein Drittel der JVMs, die unter Java 10 oder einer älteren Version laufen, vertrauen die Aufräumarbeiten dem SerialGC an, knapp 22 Prozent dem CMS und weitere 20 Prozent greifen auf den ParalelGC zurück.

Der Nachfolger des CMS, der Garbage First Garbage Collector (G1), begann seinen Siegeszug ab Version 9. Auch er arbeitet teils concurrent zur Anwendung und teils in Stop-the-World-Pausen. Aber der G1 beruht auf einer anderen Implementierung und beseitigt ein gar nicht so seltenes Problem des CMS: Unter bestimmten Umständen, etwa wenn die Heaps stark fragmentiert sind oder der GC zu viel Zeit für seine Arbeit benötigt, wird eine Full Collection ausgelöst, die dann die Anwendung doch wieder unbefriedigend lange lahmlegt. Das Tuning des CMS, mit dem sich das verhindern ließe, ist kompliziert. Statt größere Regionen auf einmal zu löschen, unterteilt der G1 sein Arbeitsgebiet in kleinere Regionen. Das optimiert den Sammelprozess, und die Anwendung friert seltener ein. Außerdem lässt sich vergleichsweise unkompliziert im Vorfeld festlegen, wie die Aufteilung der CPU-Ressourcen zwischen Anwendung und Müllbeseitigung aussehen soll. Im Zweifel erhält die Anwendung so zumindest immer die definierten Ressourcen.

Dass der G1 derzeit wahrscheinlich der beste Kompromiss zwischen Latency und Throughput ist, zeigen die Nutzerzahlen: Fast zwei Drittel derer, die ihre VMs mit Java 11 oder einer höheren Version betreiben, nutzen den G1. 28 Prozent vertrauen nach wie vor dem SerialGC, und die Anteile von CMS und ParallelGC gehen gegen null. Andere, eher als experimentell zu bezeichnende Garbage Collectors, wie zum Beispiel ZGC und Shenandoah, kommen in Produktionssystemen noch immer kaum zum Einsatz. Beide sind allerdings auch noch jünger und fokussieren sich auf niedrige Pausenzeiten, was in Anwendungsfällen von G1 nicht unbedingt Priorität war. Daher sind beide produktionsreife Versionen eher wenig in Nutzung.

Die Zukunft von Java

Für den Herbst ist der Launch der neuen Java-Version 21 LTS geplant, die Supportunterstützung ist bis 2028, der erweiterte Support bis 2031 terminiert. Sie wird einige verbesserte und neue Features, wie etwa virtuelle (leichtgewichtige) Threads, Verbesserungen im Pattern Matching und String Templates, enthalten. Das Java-Ökosystem wird sich entsprechend weiterentwickeln, mittelfristig ist der Entwicklungspfad damit gesichert.

 

Ob Java eine weit verbreitete Programmiersprache bleibt? Andere Sprachen sind auf dem Vormarsch: So machte beispielsweise Google schon 2017 Kotlin zur zweiten offiziellen Android-Programmiersprache neben Java. Ursprünglich für die JVM entwickelt, bringt die junge Sprache einige interessante Features mit; bei der Programmierung kommen Entwickler:innen mit weniger Code aus. Kotlin ist kompatibel zu Java und kann – durch Übersetzung des Codes in JavaScript – auch fürs Web genutzt werden. Auch das leicht zu erlernende Python ist immer wieder als Java-Alternative im Gespräch. Plattformunabhängige, objektorientierte oder prozedurale Programmierung und schnelle Launches machen Python beliebt. Außerdem steht die Frage im Raum, wie Oracle mit den schrumpfenden Marktanteilen umgehen und welcher Entwicklungsaufwand künftig in Java fließen wird.

Allerdings steht Java keineswegs auf wackeligen Füßen. Beinahe alle großen Tech-Unternehmen nutzen die Sprache ausgiebig selbst und Entwickler:innen können unter zahlreichen Distributionen, JDKs, Drittanwendungen und Supportangeboten wählen. Die Community ist groß und sehr aktiv – zahllose verschiedene Frameworks, Tools, Libraries und ähnliche Werkzeuge unterstützen bei der Entwicklungsarbeit. Auch wenn andere Programmiersprachen hier aufholen oder sich für bestimmte Anwendungsfälle besser eignen – an Java vorbeikommen wird man vorerst nicht.

Die statistischen Zahlen in diesem Beitrag beruhen auf dem Report „2023 State of the Java Ecosystem – an in-depth look at one of the most popular programming languages“ [2].


Links & Literatur

[1] https://newrelic.com/blog/nerd-life/state-of-java

[2] https://newrelic.com/resources/report/2023-state-of-the-java-ecosystem

The post Wo steht Java? appeared first on JAX.

]]>
Java 16: Das sind die neuen Features https://jax.de/blog/java-16-das-sind-die-neuen-features-2/ Tue, 06 Apr 2021 13:57:46 +0000 https://jax.de/?p=83008 14, 15 oder doch schon 16? Da kann man schon mal durcheinanderkommen. Durch die mittlerweile halbjährlichen Major-Releases von Java fällt es gar nicht so leicht, die aktuelle Version richtig zu benennen. Vor Kurzem hat man sich in einem Vortrag noch über die Neuerungen des JDK 14 informiert, und wenig später wurden in einem Artikel bereits die Features von Version 15 näher beleuchtet. Und da sich die Welt bekanntlich schnell weiterdreht, ist nun im März 2021 bereits das OpenJDK 16 herausgekommen.

The post Java 16: Das sind die neuen Features appeared first on JAX.

]]>
Um die Verwirrung komplettzumachen, kann man sich natürlich auch nur auf die sogenannten Long-Term-Support-Versionen (LTS) konzentrieren, die alle drei Jahre erscheinen. Das ist im Moment Java 11, wobei in der freien Wildbahn die Version 8 ebenfalls noch sehr weit verbreitet ist. Im September 2021 wird nun mit dem JDK 17 das nächste LTS-Release erscheinen. Dort werden die Neuerungen der vergangenen drei Jahre (Java 12 bis 16) finalisiert, um bis zur darauffolgenden LTS-Version (JDK 23) gut dazustehen. In den vergangenen „Zwischen“-Releases wurden die diversen, teilweise auch größeren Änderungen häufig als Previews veröffentlicht. Dadurch konnte frühzeitig Feedback eingesammelt und bereits im nächsten Release eingearbeitet werden.

(Nicht ganz so) neue Features

Die Liste der für das OpenJDK 16 umgesetzten JEPs (Java Enhancement Proposals) sieht auf den ersten Blick wieder relativ lang aus [1]:

  • 338: Vector API (Incubator)

  • 347: Enable C++14 Language Features

  • 357: Migrate from Mercurial to Git

  • 369: Migrate to GitHub

  • 376: ZGC: Concurrent Thread-Stack Processing

  • 380: Unix-Domain Socket Channels

  • 386: Alpine Linux Port

  • 387: Elastic Metaspace

  • 388: Windows/AArch64 Port

  • 389: Foreign Linker API (Incubator)

  • 390: Warnings for Value-Based Classes

  • 392: Packaging Tool

  • 393: Foreign-Memory Access API (Third Incubator)

  • 394: Pattern Matching for instanceof

  • 395: Records

  • 396: Strongly Encapsulate JDK Internals by Default

  • 397: Sealed Classes (Second Preview)

Einige der Punkte sind für Java-Entwickler aber nicht direkt relevant. Dazu zählen beispielsweise die Migration zu Git/GitHub, die Aktivierung der C++-14-Sprachfeatures und auch das Foreign Linker API als zukünftiger Ersatz für das Java Native Interface (JNI). Wir schauen am Ende dieses Artikels trotzdem genauer darauf. Werfen wir aber zunächst einen Blick auf die Funktionen, die uns Entwickler betreffen. Dabei werden dem aufmerksamen Beobachter der vergangenen Java-Releases allerdings keine bahnbrechenden Neuerungen ins Auge springen. Das hängt vermutlich mit dem bevorstehenden LTS-Release zusammen, das im Herbst 2021 erscheinen wird. In Java 17 werden die neuen Features der vergangenen Monate stabilisiert, um für die folgenden Jahre eine gute Ausgangsbasis für die notwendigen Updates und Patches zu schaffen.

Java goes Pattern Matching

Bereits seit einiger Zeit schwebt das Thema Pattern Matching im Raum und hält nach und nach Einzug in Java. Dazu sind aber zur Vorbereitung diverse Änderungen in der Sprache selbst notwendig und deshalb erfolgt die Einführung nur schrittweise. Los ging es mit den Switch Expressions bereits im JDK 12. Seit Version 14 gab es zudem bereits zwei Previews zu „Pattern Matching for instanceof“. Das wird nun mit Java 16 abgeschlossen.

Ein Pattern ist übrigens eine Kombination aus einem Prädikat (das auf eine Zielstruktur passt) und einer Menge von Variablen innerhalb dieses Musters. Diesen Variablen werden bei passenden Treffern die entsprechenden Inhalte zugewiesen und damit extrahiert. Die Intention des Pattern Matching ist letztlich die Destrukturierung von Objekten, also das Aufspalten in die Bestandteile und Zuweisen in einzelne Variablen zur weiteren Bearbeitung. Die Spezialform des Pattern Matching beim instanceof-Operator spart unnötige Casts auf die zu prüfenden Zieldatentypen. Wenn o ein String oder eine Collection ist, dann kann direkt mit den neuen Variablen (s und c) mit den entsprechenden Datentypen weitergearbeitet werden. Das Ziel ist es, Redundanzen zu vermeiden und dadurch die Lesbarkeit zu erhöhen (Listing 1).

 
boolean isNullOrEmpty( Object o ) {
  return o == null ||
    o instanceof String s && s.isBlank() ||
    o instanceof Collection c && c.isEmpty();
}

Der Unterschied zum zusätzlichen Cast mag marginal erscheinen. Für die Puristen unter den Java-Entwicklern spart das allerdings eine kleine, aber dennoch lästige Redundanz ein. Laut Brian Goetz soll die Sprache dadurch prägnanter und die Verwendung sicherer gemacht werden. Erzwungene Typumwandlungen werden vermieden und stattdessen implizit durchgeführt. Bereits die zweite Preview, die im JDK 15 erschienen war, hatte keine nennenswerten Änderungen mehr mit sich gebracht. Deswegen wird das Feature jetzt als JEP 394 finalisiert. In zukünftigen Java-Versionen wird es aber noch weitere Funktionen rund um das Pattern Matching geben, zum Beispiel in Zusammenarbeit mit den Switch Expressions.

Versiegelte Klassen

Erst das zweite Mal dabei sind die Sealed Classes. Sie wurden in Java 15 als Previewfeature eingeführt und verbleiben als JEP 397 auch im JDK 16 im Vorschaumodus. Es gibt ein paar kleine Ergänzungen gegenüber der letzten Version und vermutlich werden sie dann im LTS-Release des OpenJDK 17 finalisiert. Bis dahin möchten die Macher aber noch Rückmeldungen einsammeln.

Dieses Feature wurde übrigens im Rahmen von Projekt Amber entwickelt und gehört ebenfalls zu einer Reihe von vorbereitenden Maßnahmen für die Umsetzung von Pattern-Matching-Mechanismen in Java. Ganz konkret soll es bei der Analyse von Mustern unterstützen. Aber auch für Framework-Entwickler bieten die Sealed Classes einen interessanten Mehrwert. Die Idee ist, dass versiegelte Klassen und Interfaces entscheiden können, welche Subklassen oder -interfaces von ihnen abgeleitet werden dürfen. Bisher konnte man als Entwickler Ableitungen von Klassen nur durch Zugriffsmodifikatoren (private, protected, …) einschränken oder durch die Deklaration der Klasse als final komplett durch den Compiler untersagen. Sealed Classes bieten nun einen deklarativen Weg, um gezielt bestimmten Subklassen die Ableitung zu erlauben:

public sealed class Vehicle
  permits Car, Bike, Bus, Train {
} 

Vehicle darf nur von den vier genannten Klassen überschrieben werden. Damit wird auch dem Aufrufer deutlich gemacht, welche Subklassen erlaubt sind und überhaupt existieren. In Zukunft sollen Sealed Classes auch bei Switch Expressions eingesetzt werden können (im Rahmen des Pattern Matching). Wenn man dann je case-Zweig alle erlaubten Subklassen verwendet, kann der Einsatz des default-Blocks entfallen. Durch die Information aus der permit-Anweisung kann der Compiler sicherstellen, dass mindestens einer der Zweige aufgerufen wird (Listing 2).

 
// noch kein gültiger Code, kommt erst in späteren Java-Versionen
public BigDecimal calculateExpense(Vehicle vehicle) {
  return switch(vehicle) {
    case Car c -> calculateCarExpense(c);
    case Bike b -> calculateBikeExpense(b);
    case Bus b -> calculateBusExpense(b);
    case Train t -> calculateTrainExpense(t);
  } 
} 

Subklassen bergen immer die Gefahr, dass beim Überschreiben der Vertrag der Superklasse und damit das Liskovsche Substitutionsprinzip verletzt wird. Zum Beispiel ist es unmöglich, die Bedingungen der equals-Methode aus der Klasse Object zu erfüllen, wenn man Instanzen von einer Super- und einer Subklasse miteinander vergleichen will. Weitere Details dazu kann man in der API-Dokumentation [2] unter dem Stichwort Äquivalenzrelationen (konkret Symmetrie) nachlesen.

Sealed Classes funktionieren auch mit abstrakten Klassen. Es gibt aber ein paar Einschränkungen. Eine Sealed Class und alle erlaubten Subklassen müssen im selben Modul existieren. Im Falle von Unnamed Modules müssen sie sogar im gleichen Package liegen. Außerdem muss jede erlaubte Subklasse direkt von der Sealed Class ableiten. Die abgeleiteten Klassen dürfen übrigens wieder selbst entscheiden, ob sie weiterhin versiegelt, final oder komplett offen sein wollen. Die zentrale Versiegelung einer ganzen Klassenhierarchie von oben bis zur untersten Hierarchiestufe ist nicht möglich.

Records

Bereits zum dritten Mal dabei sind wieder die in Java 14 eingeführten record-Datentypen. Mit dem JEP 395 sollen sie nun finalisiert werden. Es gab seit der zweiten Preview (JDK 15) noch einige kleine Änderungen, die sich aus den Rückmeldungen der letzten Monate ergeben haben. Bei den Records handelt es sich um eine eingeschränkte Form der Klassendeklaration, ähnlich den Enums. Entwickelt wurden Records im Rahmen des Projektes Valhalla. Es gibt gewisse Ähnlichkeiten zu Data Classes in Kotlin und Case Classes in Scala. Auch sie sind im Umfeld der Einführung von Pattern Matching entstanden und werden in folgenden JDK-Releases noch relevanter werden. Zudem könnte die kompakte Syntax Bibliotheken wie Lombok in Zukunft zumindest zum Teil obsolet machen. Die einfache Definition einer Person mit zwei Feldern kann man nachfolgend betrachten:

public record Person(String name, Person partner ) {} 

Eine erweiterte Variante mit einem zusätzlichen Kon-struktor ist erlaubt. Dadurch lassen sich neben Pflichtfeldern auch optionale Felder abbilden (Listing 3).

public record Person(String name, Person partner ) {
public Person(String name ) { 
  this( name, null ); 
}
  public String getNameInUppercase() { 
    return name.toUpperCase(); 
  }
} 

Erzeugt wird vom Compiler eine unveränderbare (immutable) Klasse, die neben den beiden Attributen und den eigenen Methoden natürlich auch noch die Implementierungen für die Accessoren, den Konstruktor sowie equals/hashCode und toString enthält. Im Listing 4 sieht man den Pseudocode, den man dafür hätte schreiben müssen.

public final class Person extends Record {
  private final String name;
  private final Person partner;
  
  public Person(String name) { this(name, null); }
  public Person(String name, Person partner) { 
    this.name = name; this.partner = partner; 
  }
 
  public String getNameInUppercase() { 
    return name.toUpperCase(); 
  }
  public String toString() { /* ... */ }
  public final int hashCode() { /* ... */ }
  public final boolean equals(Object o) { /* ... */ }
  public String name() { return name; }
  public Person partner() { return partner; }
}

Verwendet werden Records dann wie normale Java-Klassen. Der Aufrufer merkt also gar nicht, dass ein spezieller Typ instanziiert wird (Listing 5).

var man = new Person("Adam");
var woman = new Person("Eve", man);
woman.toString(); // ==> "Person[name=Eve, partner=Person[name=Adam, partner=null]]"
 
woman.partner().name(); // ==> "Adam"
woman.getNameInUppercase(); // ==> "EVE"
 
// Deep equals
new Person("Eve", new Person("Adam")).equals( woman ); // ==> true

Records sind übrigens keine klassischen Java Beans, da sie keine echten Getter enthalten. Man kann auf die Membervariablen aber über die gleichnamigen Methoden zugreifen (name() statt getName()). Sie können im Übrigen auch Annotationen oder JavaDocs enthalten. Im Body dürfen zudem statische Felder sowie Methoden, Konstruktoren oder Instanzmethoden deklariert werden. Nicht erlaubt ist die Definition von weiteren Instanzfeldern außerhalb des record-Headers.

Zwischen Sealed Classes und den record-Typen gibt es eine Integration, wie das Beispiel in Listing 6 zeigt.

public sealed interface Expr
  permits ConstantExpr, PlusExpr, TimesExpr, NegExpr {
...
}
public record ConstantExpr(int i) implements Expr {...}
public record PlusExpr(Expr a, Expr b) implements Expr {...}
public record TimesExpr(Expr a, Expr b) implements Expr {...}
public record NegExpr(Expr e) implements Expr {...}

Eine Familie von Records kann vom gleichen Sealed Interface ableiten. Die Kombination aus Records und versiegelten Datentypen führt uns zu algebraischen Datentypen, die vor allem in funktionalen Sprachen wie Haskell zum Einsatz kommen. Konkret können wir jetzt mit Records Produkttypen und mit versiegelten Klassen Summentypen abbilden. Und auch hier schließt sich wieder der Kreis zum Pattern Matching, das ebenfalls vor allem in funktionalen Sprachen zum Einsatz kommt. Die Modernisierung von Java entwickelt sich also weiterhin in die vielversprechende Richtung der funktionalen Programmierung. Aber bitte nicht erwarten, dass Java in ein paar Jahren eine rein funktionale Sprache sein wird. Da wird es auch in Zukunft besser geeignete Kandidaten (Haskell, Clojure, …) geben. Nichtsdestotrotz wird man auch bei der Entwicklung mit Java von einigen dieser Möglichkeiten profitieren.

Weitere interessante Neuerungen

Bisher wurde die Quellen des OpenJDK in Mercurial verwaltet, einem nicht so verbreiteten Versionsverwaltungssystem. Dadurch war die Hürde für neue Entwickler relativ hoch, sich an der Entwicklung des JDK zu beteiligen. Im Rahmen des JEP 357 wurde der Sourcecode nun in ein Git-Repository migriert und sogar noch nach GitHub [3] umgezogen (JEP 369). Dabei gab es drei Hauptgründe für die Migration:

  1. Größe der Metadaten des Versionsveraltungssystems

  2. verfügbare Werkzeuge für die Versionsverwaltung

  3. Angebote an Hostingoptionen

Bei den Metadaten kam es immerhin zu einer Reduktion von 1,2 GByte auf 300 MByte im .git-Ordner. Zudem wird bei vielen Tools wie IDEs oder Texteditoren Git bereits standardmäßig unterstützt oder lässt sich leicht über Plug-ins nachrüsten. Git hat den Markt für verteilte Versionsverwaltungssysteme in den letzten Jahren nahezu überrollt. Der Schritt, die Quellen des OpenJDK nach GitHub umzuziehen, ist also nachvollziehbar. Um alle relevanten Informationen wie die Historie und Tags mit zu übertragen, wurde eigens ein kleines Tool geschrieben. Dieses hat die Mercurial Commit Messsages in das Git-Format überführt.

Wer schon sehr lange in der Java-Welt unterwegs ist, wird sich vielleicht noch an das Java Native Interface (JNI) erinnern. Damit kann man nativen C-Code aus Java heraus aufrufen. Der Ansatz ist aber relativ aufwendig und fragil. Das Foreign Linker API (JEP 389) bietet nun einen statisch typisierten, rein Java-basierten Zugriff auf nativen Code. Zusammen mit dem Foreign-Memory Access API (JEP 393) kann diese Schnittstelle den bisher fehleranfälligen Prozess der Anbindung einer nativen Bibliothek beträchtlich vereinfachen. Mit Letzterer bekommen Java-Anwendungen die Möglichkeit, außerhalb des Heap zusätzlichen Speicher zu allokieren.

Wer häufig mit primitiven Wrapper-Klassen (Integer, Boolean, …) arbeitet, wird ab dem JDK 16 womöglich über neue Deprecation-for-Removal-Warnungen stolpern. Das betrifft sowohl die Konstruktoren mit dem Stringparameter als auch mit dem jeweiligen primitiven Datentyp als Argument (int bei Integer). Hinter dieser Maßnahme steckt auch das Projekt Valhalla. Dort strebt man die Erweiterung des Java-Programmiermodells in Form von primitiven Klassen an. Diese primitiven Klassen sollen keine Identität besitzen und dadurch auch leicht vom Compiler bzw. dem Laufzeit-Interpreter „ge-inlined“ werden können. Dadurch lassen sie sich frei zwischen Speicherorten kopieren und als Werte von Instanzfeldern kodieren.

Ebenfalls dem Vorschaufeature entwachsen ist mit dem OpenJDK 16 das jpackage-Werkzeug. Es unterstützt native Paketformate, um den Nutzern eine einfache Installation zu ermöglichen, inklusive der Angabe von Startparametern zum Zeitpunkt der Paketierung. Zu den Formaten gehören msi und exe unter Windows, pkg und dmg unter macOS, sowie deb und rpm unter Linux. Das Tool kann direkt über die Befehlszeile oder auch programmatisch aufgerufen werden.

Durch das in Java 9 eingeführte Java Platform Modul System (JPMS) können nun JDK-interne Klassen vor dem Zugriff von außen geschützt werden. Bisher gab es zum Übergang aber die eingeschränkte Kapselung als Default, d. h., dass interne APIs weiterhin verwendet werden konnten. Dieses Schlupfloch wird mit dem OpenJDK 16 geschlossen. Die neue Standardeinstellung ist die strikte Kapselung der JDK-Internas, außer für sehr kritische interne APIs wie misc.Unsafe. Das Ziel dieser Maßnahme ist die Erhöhung der Sicherheit und Wartbarkeit des JDK. Man möchte die Entwickler ermutigen, alte, auf Internas basierende Lösungen zukünftig für den Zugriff auf Standard-APIs umzubauen. Somit sollen sowohl Java-Entwickler als auch Endbenutzer viel problemloser auf zukünftige Versionen updaten können.

Neben den prominenten JEPs gibt es in jeder neuen JDK-Version noch viele kleine Änderungen, zum Beispiel an der Java-Klassenbibliothek. Mit dem Java Version Almanac [4] kann man sehr einfach und kompakt die Differenzen zwischen den Releases, aber auch bei Versionssprüngen von z. B. JDK 8 auf 16 einsehen. Aus Entwicklersicht sind besonders zwei Neuerungen an der Klasse Stream interessant. Stream.toList() bietet eine prägnantere und im Einsatz mit parallel() meist auch effizientere Alternative zu Stream.collect(Collectors.toList()). Als Ergebnis wird eine nicht veränderbare (unmodifiable) ArrayList zurückgegeben. Weitere Informationen kann man der API-Dokumentation [5] oder dem Artikel von Donald Raab [6] entnehmen. Die zweite Neuerung in der Klasse Stream ist die in diversen Ausprägungen hinzugekommene Methode mapMulti(BiConsumer). Sie stellt eine imperative und schnellere Alternative zu flatMap dar. Nicolai Parlog hat die neue Funktion in einem Blogpost näher unter die Lupe genommen und zum Vergleich Performancemessungen durchgeführt [7].

Ausblick

Schon kurz vor der Fertigstellung des OpenJDK 16 wurden die ersten Features für das JDK 17 angekündigt [8]. Unter anderem soll es eine neue Rendering Pipeline für macOS und die Erweiterung des Pseudozufallszahlengenerators geben. Höchstwahrscheinlich werden wir allerdings keine weiteren großen, prominenten Änderungen sehen. Schließlich werden mit Java 17 die Entwicklungen der vergangenen 36 Monate abgeschlossen und es wird eine Version bereitgestellt, die wieder für die nächsten Jahre mit Updates und Sicherheitpatches versorgt werden muss. Die wirklich spannenden Neuerungen und Syntaxerweiterungen werden wir dann voraussichtlich erst wieder in der Version 18 sehen. Ab da hat Oracle dann wieder zweieinhalb Jahre Zeit, Feedback zu Previewfeatures einzuarbeiten und diese abzurunden.

Der 2018 eingeschlagene Weg – der anfangs nicht unumstritten war – wird weiterhin konsequent verfolgt. Die halbjährlichen Updates der Programmiersprache und der Plattform Java geben uns Entwicklern die einfache Möglichkeit, regelmäßig neue Funktionen ausprobieren zu können. Potenziell kann man sogar sein Produktivsystem alle sechs Monate aktualisieren und vermeidet langwierige Migrationsaufwände, die sonst irgendwann anfallen würden. Konservative Kunden können aber trotzdem LTS-Versionen einsetzen, die wie frühere Releases (vor Java 8) über mehrere Jahre mit Updates versorgt werden. Dabei hat man mittlerweile die freie Wahl zwischen verschiedenen Anbietern. Neben kommerziellen Versionen (Oracle JDK und andere) gibt es auch genügend JDKs mit freien Updates (allen voran AdoptOpenJDK). Das Java-Ökosystem ist also lebendiger denn je und weiterhin sehr innovativ. Konkurrenz wie beispielsweise Python (hauptsächlich wegen Machine/Deep Learning), Go und die C-basierten Sprachen beleben das Geschäft. Java, das besonders im Unternehmensanwendungsumfeld vertreten ist, wird aber weiterhin ein gewichtiges Wort mitreden.

 

The post Java 16: Das sind die neuen Features appeared first on JAX.

]]>