Java – die Neuerungen in Version 9 bis 14: Modularisierung, Syntax- und API-Erweiterungen
Von Michael Inden
()
Über dieses E-Book
- Aktuelle Infos bis zur neuesten Java-Version
- Vertiefen Sie Ihr Know-how durch praktische Übungen
- Lernen Sie die wichtigen Änderungen kompakt kennen
Sie besitzen bereits solides Java-Know-how und möchten sich prägnant über die wichtigsten Neuerungen in den Java-Versionen 9 bis 14 informieren und Ihr Wissen auf den neuesten Stand bringen? Dann ist dieses Buch genau das Richtige für Sie. Vertiefen können Sie Ihr Wissen in diesem Buch zusätzlich durch eine Vielzahl an Übungen.
Mehr von Michael Inden lesen
Einfach Python: Gleich richtig programmieren lernen Bewertung: 0 von 5 Sternen0 BewertungenPython lernen – kurz & gut Bewertung: 0 von 5 Sternen0 BewertungenEinfach Java: Gleich richtig programmieren lernen Bewertung: 0 von 5 Sternen0 BewertungenPython Challenge: Fit für Prüfung, Job-Interview und Praxis – mit 100 Aufgaben und Musterlösungen Bewertung: 0 von 5 Sternen0 BewertungenDer Java-Profi: Persistenzlösungen und REST-Services: Datenaustauschformate, Datenbankentwicklung und verteilte Anwendungen Bewertung: 0 von 5 Sternen0 BewertungenDer Weg zum Java-Profi: Konzepte und Techniken für die professionelle Java-Entwicklung Bewertung: 0 von 5 Sternen0 BewertungenJava 9 – Die Neuerungen: Syntax- und API-Erweiterungen und Modularisierung im Überblick Bewertung: 0 von 5 Sternen0 BewertungenJava – die Neuerungen in Version 17 LTS, 18 und 19 Bewertung: 0 von 5 Sternen0 BewertungenJava 8 - Die Neuerungen: Lambdas, Streams, Date and Time API und JavaFX 8 im Überblick Bewertung: 0 von 5 Sternen0 BewertungenJava – die Neuerungen in Version 9 bis 12: Modularisierung, Syntax- und API-Erweiterungen Bewertung: 0 von 5 Sternen0 Bewertungen
Ähnlich wie Java – die Neuerungen in Version 9 bis 14
Ähnliche E-Books
Java – die Neuerungen in Version 9 bis 12: Modularisierung, Syntax- und API-Erweiterungen Bewertung: 0 von 5 Sternen0 BewertungenJava 8 - Die Neuerungen: Lambdas, Streams, Date and Time API und JavaFX 8 im Überblick Bewertung: 0 von 5 Sternen0 BewertungenKompaktkurs C# 7 Bewertung: 0 von 5 Sternen0 BewertungenAutomatisiertes Testen: Testautomatisierung mit Geb und ScalaTest Bewertung: 0 von 5 Sternen0 BewertungenModerne Datenzugriffslösungen mit Entity Framework 6 Bewertung: 0 von 5 Sternen0 BewertungenJavaScript effektiv: 68 Dinge, die ein guter JavaScript-Entwickler wissen sollte Bewertung: 0 von 5 Sternen0 BewertungenLDAP für Java-Entwickler: Einstieg und Integration (Neuauflage) Bewertung: 0 von 5 Sternen0 BewertungenBigData mit JavaScript visualisieren: D3.js für die Darstellung großer Datenmengen einsetzen Bewertung: 0 von 5 Sternen0 BewertungenSprechen Sie Java?: Eine Einführung in das systematische Programmieren Bewertung: 4 von 5 Sternen4/5F#: Ein praktischer Einstieg Bewertung: 0 von 5 Sternen0 BewertungenServer-Infrastrukturen mit Microsoft Windows Server Technologien: Alle Themen für das Microsoft Seminar und die Zertifizierungsprüfung MOC 20413 Bewertung: 0 von 5 Sternen0 BewertungenJavaScript und Ajax: Das Praxisbuch für Web-Entwickler Bewertung: 0 von 5 Sternen0 BewertungenEclipse 4: Rich Clients mit dem Eclipse 4.2 SDK Bewertung: 0 von 5 Sternen0 BewertungenGroovy – kurz & gut Bewertung: 0 von 5 Sternen0 BewertungenNext Level JavaScript: Schlagworte Bewertung: 0 von 5 Sternen0 BewertungenDocker und die Containerwelt: Einstieg und Expertentipps rund um Docker-Container Bewertung: 1 von 5 Sternen1/5Vue.js für alle: Wissenswertes für Einsteiger und Experten Bewertung: 0 von 5 Sternen0 BewertungenGraphQL: Eine Einführung in APIs mit GraphQL Bewertung: 0 von 5 Sternen0 Bewertungen.NET-Praxis: Tipps und Tricks zu .NET und Visual Studio Bewertung: 0 von 5 Sternen0 BewertungenMicrosoft Dynamics NAV 2018 RapidStart Bewertung: 0 von 5 Sternen0 BewertungenSAP User Interface Strategien: zielgruppengerechte Bewertung und Einordnung Bewertung: 0 von 5 Sternen0 BewertungenWeb-Applikationen entwickeln mit NoSQL: Das Buch für Datenbank-Einsteiger und Profis! Bewertung: 0 von 5 Sternen0 BewertungenDatenbank-Tuning - Slow Queries und MySQL-Performance: Slow Queries und MySQL-Performance Bewertung: 0 von 5 Sternen0 BewertungenSharePoint Kompendium - Bd. 13 Bewertung: 0 von 5 Sternen0 BewertungenCloud-Services testen: Von der Risikobetrachtung zu wirksamen Testmaßnahmen Bewertung: 0 von 5 Sternen0 BewertungenDie wissenschaftliche Arbeit mit LaTeX: unter Verwendung von LuaTeX, KOMA-Script und Biber/BibLaTeX Bewertung: 0 von 5 Sternen0 BewertungenC++: Kurzportträt einer zeitlosen Sprache Bewertung: 0 von 5 Sternen0 BewertungenDas Internet der Dinge als Basis der digitalen Automation: Beiträge zu den Bachelor- und Masterseminaren 2018 im Fachbereich Technik der Hochschule Trier Bewertung: 0 von 5 Sternen0 BewertungenSQL-Abfragen optimieren: Was Entwickler über Performance wissen müssen Bewertung: 0 von 5 Sternen0 BewertungenHTML5 Security Bewertung: 0 von 5 Sternen0 Bewertungen
Programmieren für Sie
Die ultimative FRITZ!Box Bibel - Das Praxisbuch 2. aktualisierte Auflage - mit vielen Insider Tipps und Tricks - komplett in Farbe Bewertung: 0 von 5 Sternen0 BewertungenSQL – kurz & gut Bewertung: 0 von 5 Sternen0 BewertungenPython-Grundlagen Bewertung: 0 von 5 Sternen0 BewertungenC++: Eine kompakte Einführung Bewertung: 0 von 5 Sternen0 BewertungenDie nicht zu kurze Kurzeinführung in MATLAB: Erste Schritte in MATLAB Bewertung: 0 von 5 Sternen0 BewertungenPython | Schritt für Schritt Programmieren lernen: Der ultimative Anfänger Guide für einen einfachen & schnellen Einstieg Bewertung: 0 von 5 Sternen0 BewertungenHTML5-Programmierung von Kopf bis Fuß: Webanwendungen mit HTML5 und JavaScript Bewertung: 0 von 5 Sternen0 BewertungenEigene Spiele programmieren – Python lernen: Der spielerische Weg zur Programmiersprache Bewertung: 0 von 5 Sternen0 BewertungenProgrammieren lernen mit Python 3: Schnelleinstieg für Beginner Bewertung: 0 von 5 Sternen0 BewertungenPython kurz & gut: Für Python 3.x und 2.7 Bewertung: 3 von 5 Sternen3/5Think Python: Systematisch programmieren lernen mit Python Bewertung: 0 von 5 Sternen0 BewertungenEinstieg in TypeScript: Grundlagen für Entwickler Bewertung: 0 von 5 Sternen0 BewertungenJavaScript kinderleicht!: Einfach programmieren lernen mit der Sprache des Web Bewertung: 0 von 5 Sternen0 BewertungenPowerShell: Anwendung und effektive Nutzung Bewertung: 5 von 5 Sternen5/5Vue.js kurz & gut Bewertung: 0 von 5 Sternen0 BewertungenProgrammieren für Einsteiger: Teil 1 Bewertung: 0 von 5 Sternen0 BewertungenHacken mit Python und Kali-Linux: Entwicklung eigener Hackingtools mit Python unter Kali-Linux Bewertung: 0 von 5 Sternen0 BewertungenDas große Python3 Workbook: Mit vielen Beispielen und Übungen - Programmieren leicht gemacht! Bewertung: 4 von 5 Sternen4/5Microcontroller für das IoT Bewertung: 0 von 5 Sternen0 BewertungenMikrocontroller in der Elektronik: Mikrocontroller programmieren und in der Praxis einsetzen Bewertung: 0 von 5 Sternen0 BewertungenPowerprojekte mit Arduino und C: Schluss mit dem frustrierenden Ausprobieren von Code-Schnipseln! Bewertung: 0 von 5 Sternen0 BewertungenPython programmieren lernen: Der spielerische Einstieg mit Minecraft Bewertung: 0 von 5 Sternen0 BewertungenRaspberry Pi: Einstieg • Optimierung • Projekte Bewertung: 5 von 5 Sternen5/5Android-Programmierung kurz & gut Bewertung: 0 von 5 Sternen0 BewertungenAlgorithmen: Grundlagen und Implementierung Bewertung: 0 von 5 Sternen0 BewertungenTraumjob IT 2021: Branchenüberblick, Erfahrungsberichte und Tipps zum Berufseinstieg Bewertung: 5 von 5 Sternen5/5Microsoft Word 2016 (Microsoft Press): Einfache Anleitungen für wichtige Aufgaben Bewertung: 0 von 5 Sternen0 BewertungenLinux Grundlagen - Ein Einstieg in das Linux-Betriebssystem Bewertung: 0 von 5 Sternen0 BewertungenJavaScript kurz & gut Bewertung: 3 von 5 Sternen3/5Programmieren von Kopf bis Fuß Bewertung: 4 von 5 Sternen4/5
Rezensionen für Java – die Neuerungen in Version 9 bis 14
0 Bewertungen0 Rezensionen
Buchvorschau
Java – die Neuerungen in Version 9 bis 14 - Michael Inden
1Einleitung
Früher wurden Java-Releases aufgrund unfertiger Features häufiger verschoben. Um dem entgegenzuwirken, hat Oracle nach dem Erscheinen von Java 9 auf einen halbjährlichen Releasezyklus umgestellt. Das erlaubt es, die jeweils bis zu diesem Zeitpunkt fertig implementierten Funktionalitäten zu veröffentlichen. Zwar kann diese schnelle Releasefolge eine größere Herausforderung für Toolhersteller sein, für uns als Entwickler ist es aber oftmals positiv, weil wir potenziell weniger lang auf neue Features warten müssen. Das konnte früher recht mühsam sein, wie die letzten Jahre gezeigt haben. Ein paar Gedanken dazu greift der nachfolgende Hinweiskasten auf.
Allerdings gilt das Positive vor allem für eigene Hobbyprojekte, weil man dort mit den Neuerungen experimentieren kann und weniger durch Restriktionen eingeschränkt ist. Im professionellen Einsatz wird man eher auf Kontinuität und die Verfügbarkeit von Security Updates setzen, weshalb in diesem Kontext vermutlich nur LTS-Versionen in Betracht kommen, um Migrationsaufwände kalkulierbar und zeitlich besser planbar zu halten.
Hinweis: Oracles neue Releasepolitik
Bis einschließlich Java 9 wurden neue Java-Versionen immer Feature-basiert veröffentlicht. Das hatte in der Vergangenheit oftmals und mitunter auch beträchtliche Verschiebungen des geplanten Releasetermins zur Folge, wenn für die Version wesentliche Features noch nicht fertig waren. Insbesondere deshalb verzögerten sich Java 8 und Java 9 um mehrere Monate bzw. sogar über ein Jahr: Rund 3,5 Jahre nach dem Erscheinen von JDK 8 im März 2014 ging Java mit Version 9 im September 2017 an den Start. Wieder einmal musste die Java-Gemeinde auf die Veröffentlichung der Version 9 des JDKs länger warten – es gab gleich mehrere Verschiebungen, zunächst von September 2016 auf März 2017, dann auf Juli 2017 und schließlich auf September 2017.
Mit einer zeitbasierten Releasestrategie möchte man derartigen Verzögerungen entgegenwirken, indem jedes halbe Jahr eine neue Java-Version veröffentlicht wird, die all jene Features enthält, die bereits fertig sind. Alle drei Jahre ist dann eine LTS-Version (Long Term Support) geplant. Eine solche ist in etwa vergleichbar mit den früheren Major-Versionen.
Was erwartet Sie im Folgenden?
Dieses Buch gibt einen fundierten Überblick über diverse wesentliche Erweiterungen in den JDKs 9 bis 14. Es werden unter anderem folgende Themen behandelt:
API- und SyntaxerweiterungenWir schauen uns verschiedene Änderungen an der Syntax von Java an. Neben Erweiterungen bei der @Deprecated-Annotation widmen wir uns Details zu Bezeichnern, dem Diamond Operator und vor allem gehe ich kritisch auf das Feature privater Methoden in Interfaces ein. Für Java 10 und 11 thematisiere ich die Syntaxerweiterung var als Möglichkeit zur Definition lokaler Variablen bzw. zur Verwendung in Lambdas. Im Kontext von instanceof ist es mit Java 14 möglich, künstliche Hilfsvariablen und unschöne Casts zu vermeiden.
Kommen wir zu den APIs: In Java 9 wurden diverse APIs ergänzt oder neu eingeführt. Auch Bestehendes, wie z. B. das Stream-API oder die Klasse Optional
JVM-ÄnderungenIn jeweils eigenen Abschnitten beschäftigen wir uns mit Änderungen in der JVM, für JDK 9 etwa in Bezug auf die Nummerierung von Java-Versionen oder javadoc. Zudem kann für Quereinsteiger und Neulinge die durch das Tool jshell bereitgestellte Java-Konsole mit REPL-Unterstützung (Read-Eval-Print-Loop) erste Experimente und Gehversuche erleichtern, ohne dafür den Compiler oder eine IDE bemühen zu müssen. Mit Java 11 kommt ein neuer Garbage Collector sowie mit dem Feature »Launch Single-File Source-Code Programs« die Möglichkeit, Java-Klassen ohne explizite vorherige Kompilierung ausführen zu lassen und somit für Scripting einsetzen zu können. Java 12 bietet als wesentliche Neuerung die Integration des Microbenchmark-Frameworks JMH (Java Microbenchmarking Harness).
ModularisierungDie Modularisierung adressiert zwei typische Probleme größerer Java-Applikationen. Zum einen ist dies die sogenannte JAR-Hell, womit gemeint ist, dass sich im CLASSPATH verschiedene JARs mit zum Teil inhaltlichen Überschneidungen (unterschiedliche Versionen mit Abweichungen in Packages oder gleiche Klassen, jedoch mit anderem Bytecode) befinden. Dabei kann allerdings nicht sichergestellt werden, wann welche Klasse aus welchem JAR eingebunden wird. Zum anderen sind als public definierte Typen beliebig von anderen Packages aus zugreifbar. Das erschwert die Kapselung. Mit JDK 9 lassen sich eigenständige Softwarekomponenten (Module) mit einer Sichtbarkeitssteuerung definieren. Das hat allerdings weitreichende Konsequenzen: Sofern man Module verwendet, kann man Programme mit JDK 9 nicht mehr ohne Weiteres wie gewohnt starten, wenn diese externe Abhängigkeiten besitzen. Das liegt vor allem daran, dass Abhängigkeiten nun beim Programmstart geprüft und dazu explizit beschrieben werden müssen.
Es gibt aber einen rein auf dem CLASSPATH basierenden Kompatibilitätsmodus, der ein Arbeiten wie bis einschließlich JDK 8 gewohnt ermöglicht.
Tipp: Beispiele und der Kompatibilitätsmodus
Zum Ausprobieren verschiedener Neuerungen aus JDK 9 bis 14 werden wir kleine Beispielapplikationen in main()-Methoden erstellen. Dabei ist es für erste Experimente und für die Migration bestehender Anwendungen von großem Vorteil, dass man das an sich modularisierte JDK auch ohne eigene Module und ihre Sichtbarkeitsbeschränkungen betreiben kann. In diesem Kompatibilitätsmodus wird wie zuvor bei Java 8 mit .class-Dateien, JARs und dem CLASSPATH gearbeitet. Für zukünftige Projekte wird man mitunter Module nutzen wollen. Das schauen wir uns in eigenen Kapiteln an.
Ausprobieren der Java-14-Beispiele
Durch die kurzen Releasezyklen werden mittlerweile einige Features als Previews der Entwicklergemeinde vorgestellt. Möchte man diese nutzen, so sind in den IDEs und Build-Tools gewisse Parametrierungen sowohl beim Kompilieren als auch beim Ausführen nötig, etwa wie im folgenden Beispiel:
java --enable-preview -cp build/libs/Java14Examples.jar \
java14.RecordExamples
Details dazu werden in Abschnitt 9.4 beschrieben.
Entdeckungsreise JDK 9 bis 14 – Wünsche an die Leser
Ich wünsche allen Lesern viel Freude mit diesem Buch sowie einige neue Erkenntnisse und viel Spaß beim Experimentieren mit JDK 9 bis 14. Möge Ihnen der Umstieg auf die von Ihnen bevorzugte Java-Version und die Erstellung modularer Applikationen oder die Migration bestehender Anwendungen durch die Lektüre meines Buchs leichter fallen.
Wenn Sie zunächst eine Auffrischung Ihres Wissens zu Java 8 und seinen Neuerungen benötigen, bietet sich ein Blick in den Anhang A an.
Teil I
Neuerungen in Java 9 bis 11
2Syntaxerweiterungen in JDK 9 bis 11
In JDK 9 finden sich verschiedene kleinere Syntaxerweiterungen, die wir uns hier anschauen. Außerdem lernen wir mit var die sogenannte Local Variable Type Inference als Syntaxerweiterung in Java 10 kennen, die mit Java 11 leicht erweitert wurde.
2.1Anonyme innere Klassen und der Diamond Operator
Bei der Definition anonymer innerer Klassen konnte man den Diamond Operator bis JDK 8 leider nicht nutzen, sondern der Typ aus der Deklaration war auch bei der Definition explizit anzugeben. Praktischerweise ist es mit JDK 9 (endlich) möglich, auf diese redundante Typangabe zu verzichten. Als Beispiel dient die Definition eines Komparators mit dem Interface java.util.Comparator
Beispiel mit JDK 8
Bis JDK 8 musste man bei der Definition einer anonymen inneren Klasse den Typ noch wie folgt angeben:
final Comparator
{
...
};
Beispiel mit JDK 9
Die Änderung zu JDK 8 ist kaum sichtbar: Mit JDK 9 ist es nun erlaubt, die Typangabe wegzulassen und somit den Diamond Operator zu verwenden, wie wir dies von anderen Variablendefinitionen bereits gewohnt sind:
final Comparator
{
...
};
Für Komparatoren gibt es seit JDK 8 zwei Neuerungen, die die Definition erleichtern. Im Anhang A behandle ich unter anderem auch die Erweiterungen bei Komparatoren.
2.2Erweiterung der @Deprecated-Annotation
Die @Deprecated-Annotation dient bekanntlich zum Markieren von obsoletem Sourcecode und besaß bislang keine Parameter. Das ändert sich mit JDK 9: Die @Deprecated-Annotation wurde um die zwei Parameter since und forRemoval erweitert. Die Annotation ist nun im JDK wie folgt definiert:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER,
TYPE})
public @interface Deprecated {
/**
* Returns the version in which the annotated element became deprecated.
* The version string is in the same format and namespace as the value of
* the {@code @since} javadoc tag. The default value is the empty
* string.
*
* @return the version string
* @since 9
*/
String since() default ;
/**
* Indicates whether the annotated element is subject to removal in a
* future version. The default value is {@code false}.
*
* @return whether the element is subject to removal
* @since 9
*/
boolean forRemoval() default false;
}
Diese Erweiterung wurde nötig, weil für Folgereleases von Java 9 geplant ist, veraltete Funktionalität aus dem JDK zu entfernen, statt sie – wie bislang für Java üblich – aus Gründen der Rückwärtskompatibilität ewig beizubehalten. Das folgende Beispiel zeigt eine Anwendung, wie sie aus dem JDK stammen könnte:
@Deprecated(since = 1.5
, forRemoval = true)
Mithilfe der beiden Parameter kann man für veralteten Sourcecode angeben, in welcher Version (since) dieser mit der Markierung als @Deprecated versehen wurde und ob der Wunsch besteht, die markierten Sourcecode-Teile in zukünftigen Versionen zu entfernen (forRemoval). Weil beide Parameter Defaultwerte besitzen (since = und forRemoval = false), können die Angaben jeweils für sich alleine stehen oder ganz entfallen.
Diese Erweiterung der @Deprecated-Annotation kann man selbstverständlich auch für eigenen Sourcecode nutzen und so anzeigen, dass gewisse Funktionalitäten für die Zukunft nicht mehr angeboten werden sollen. Darüber hinaus empfiehlt es sich, in einem Javadoc-Kommentar das @deprecated-Tag zu verwenden und dort den Grund der Deprecation und eine empfohlene Alternative aufzuführen. Nachfolgend ist dies exemplarisch für eine veraltete Methode someOldMethod() gezeigt:
/**
* @deprecated this method is replaced by someNewMethod()
* ({@link #someNewMethod()}) which is more stable
*/
@Deprecated(since = 7.2
, forRemoval = true)
private static void someOldMethod()
{
// ...
}
2.3Private Methoden in Interfaces
Allgemein bekannt ist, dass Interfaces der Definition von Schnittstellen dienen. Leider verlieren in Java die Interfaces immer mehr von ihrer eigentlichen Bedeutung: Seit Java 8 sind statische Methoden und Defaultmethoden in Interfaces erlaubt. Mit beiden kann man Implementierungen vorgeben.¹ Dadurch unterscheiden sich Interfaces kaum mehr von einer abstrakten Klasse: Letztere können ergänzend einen Zustand in Form von Attributen besitzen, was in Interfaces (noch) nicht geht.
Mit JDK 9 wurde der Unterschied zwischen Interfaces und abstrakten Klassen nochmals verringert, weil sich nun auch private Methoden in Interfaces definieren lassen. Das Argument dafür war, dass sich damit die Duplikation von Sourcecode in Defaultmethoden reduzieren ließe. Das mag richtig sein. Allerdings ist es für die meisten Anwendungsprogrammierer eher fraglich, ob diese jemals Defaultmethoden selbst implementieren sollten. Für Framework-Entwickler können private Methoden in Interfaces eventuell von Nutzen sein.
Beispiel
Schauen wir uns zur Demonstration privater Methoden in Interfaces das nachfolgende Listing und vor allem die private Methode myPrivateCalcSum(int, int) sowie deren Aufruf aus den beiden öffentlichen Defaultmethoden an:
public interface PrivateMethodsExample
{
// Tatsächliche Schnittstellendefinition - public abstract ist optional
public abstract int method1();
public abstract String method2();
public default int sum(final String num1, final String num2)
{
final int value1 = Integer.parseInt(num1);
final int value2 = Integer.parseInt(num2);
return myPrivateCalcSum(value1, value2);
}
public default int sum(final int value1, final int value2)
{
return myPrivateCalcSum(value1, value2);
}
// Neu und unschön in JDK 9
private int myPrivateCalcSum(final int value1, final int value2)
{
return value1 + value2;
}
}
Kommentar
Vielleicht fragen Sie sich, warum ich den privaten Methoden in Interfaces so ablehnend gegenüberstehe. Tatsächlich wurde die Büchse der Pandora bereits mit JDK 8 und den Defaultmethoden geöffnet. Die privaten Methoden mögen für Framework-Entwickler mitunter praktisch sein, jedoch besteht die Gefahr, dass sie für »normale« Entwickler noch attraktiver werden und von diesen somit ohne großes Hinterfragen zur Applikationsentwicklung eingesetzt werden. Das wäre aber im Hinblick auf das Design und die Klarheit von Business-Applikationen ein Schritt in die falsche Richtung.² Dadurch wird unter Umständen dem Schnittstellenentwurf weniger Aufmerksamkeit gewidmet, basierend auf der Annahme, dass benötigte Funktionalität immer noch nachträglich hinzugefügt werden kann.
2.4Verbotener Bezeichner ’_’
Bei den Bezeichnern gibt es eine kleine Änderung: Der Compiler erlaubt mit JDK 9 das Zeichen _ (Unterstrich) nicht mehr als Bezeichner.
Während folgende Zeile mit JDK 8 noch kompilierte
final String _ = Underline
;
produziert der Java-Compiler mit JDK 9 folgende Fehlermeldung:
as of release 9, ’_’ is a keyword, and may not be used as an identifier
Ich persönlich halte ein einzelnes Zeichen als Variablenbezeichner fast immer für einen Bad Smell und insbesondere gilt dies für den Unterstrich. Vermutlich sehen Sie dies ähnlich. Insofern stellt diese Änderung wohl eher selten ein Problem dar.
2.5Syntaxerweiterung var (JDK 10 und 11)
Wie einleitend erwähnt, bietet Java 10 die Local Variable Type Inference als Syntaxerweiterung. Diese erlaubt es, auf die explizite Typangabe auf der linken Seite einer Variablendefinition zu verzichten, sofern sich der konkrete Typ für eine lokale Variable anhand der Definition auf der rechten Seite der Zuweisung vom Compiler ermitteln lässt.
Einführende Beispiele
Schauen wir uns einige einführende Beispiele für die Kurzschreibweise mit var für Variablendefinitionen an:
var name = Peter
; // var => String
var chars = name.toCharArray(); // var => char[]
var mike = new Person(Mike
, 47); // var => Person
var hash = mike.hashCode(); // var => int
Insbesondere im Zusammenhang mit generischen Containern spielt die Local Variable Type Inference ihre Vorteile aus:
// var => ArrayList
var names = new ArrayList
names.add(Tim
);
names.add(Tom
);
names.add(Jerry
);
// var => Map
var personAgeMapping = Map.of(Tim
, 47L, Tom
, 12L,
Michael
, 47L, Max
, 25L);
Vor allem wenn die Typangaben mehrere generische Parameter umfassen, kann var den Sourcecode deutlich kürzer und mitunter lesbarer machen. Betrachten wir als Beispiel eine Verschachtelung von Typen analog zu den folgenden:
Set>
Map>>
In solchen Fällen spart var einiges an Schreibarbeit – zusätzlich erfolgt hier noch ein statischer Import verschiedener Kollektoren, um die Lesbarkeit zu steigern:
// var => Set
var entries = personAgeMapping.entrySet();
// var => Map
var filteredPersons = personAgeMapping.entrySet().stream().
collect(groupingBy(firstChar,
filtering(isAdult, toSet())));
Im Beispiel werden zur Beschreibung der Gruppierung und zur Filterung folgende zwei Lambdas genutzt, worauf ich gleich nochmals genauer eingehe:
Function
entry -> entry.getKey().charAt(0);
Predicate
Hilfestellung in IDEs
So angenehm die Kurzschreibweise in der Regel auch ist, so wünschenswert ist manchmal eine Expansion in oder ein Hinweis auf den konkreten Typ. In beiden Fällen ist der intelligente Tooltip in Eclipse hilfreich, wie folgende Abbildung 2-1 zeigt.
Abbildung 2-1 Hilfestellung zu var in Eclipse
Vermutlich eher selten besteht das Bedürfnis, doch wieder den konkreten Typ statt var zu nutzen. Praktischerweise existieren Quick Fixes in den gebräuchlichen IDEs, um zwischen konkretem Typ und var leicht hin- und herzuwechseln.
Lambda-Ausdrücke und var
Eine Kleinigkeit sollten wir noch betrachten: Im vorherigen Beispiel kommen beim Aufbereiten der Map folgende zwei Lambdas zum Einsatz, um die Funktionalität zu realisieren:
Function
entry -> entry.getKey().charAt(0);
Predicate
Wäre es nicht wünschenswert, auch hier die Typangabe mit var abzukürzen? Eigentlich ja! Warum dies nicht geht, erkläre ich im Folgenden.
Der Compiler kann allein auf Basis dieser Lambdas den konkreten Typ nicht ermitteln. Somit ist keine Umwandlung in var möglich, sondern führt zur Fehlermeldung »lambda expression needs an explicit target-type«. Wollte man var trotzdem nutzen, so müsste man folgenden Cast einfügen:
var isAdultVar =
(Predicate
Insgesamt sieht man, dass var für Lambda-Ausdrücke eher ungeeignet ist. Das ist insofern schade, weil hier einige Schreibarbeit gespart werden könnte.
Beschränkungen
Rekapitulieren wir kurz: var ist für lokale Variablen gedacht, die direkt initialisiert werden. Damit ist es im Speziellen auch für Variablen in for-Schleifen und try-with-resources geeignet. Darüber hinaus scheint mitunter wünschenswert, var zur Deklaration von Attributen, Parametern oder Rückgabetypen nutzen zu können. Das ist ebenso wie für Lambdas jedoch nicht möglich, weil der Typ vom Compiler nicht eindeutig ermittelt werden kann.
Restriktion auf exakten TypBeim Einsatz von var sollte man wissen, dass immer der exakte Typ verwendet wird und nicht ein Basistyp, wie man es für Collections getreu dem Paradigma »program against interfaces« vielfach nutzt. Beachten Sie bitte, dass im Folgenden die Variable names deshalb nicht vom Typ List
// var => ArrayList
var names = new ArrayList
names = new LinkedList
Versucht man, eine Instanz einer LinkedList
Weitere syntaktische BesonderheitenEs gibt weitere Dinge, die zu Kompilierfehlern führen. Das sind eine fehlende Wertangabe und auch eine fehlende Typangabe bei direkten Array-Initialisierungen:
var justDeclaration; // keine Wertangabe / Definition
var numbers = {0, 1, 2}; // fehlende Typangabe
Fallstrick
Auf einen Fallstrick beim Einsatz von var möchte ich unbedingt noch eingehen: Manchmal ist man versucht, ohne viel nachzudenken, die Typangabe auf der linken Seite direkt mit var zu ersetzen. Schauen wir auf ein harmlos wirkendes Beispiel einer auf String typisierten Liste:
List
names.add(Expected
);
// names.add(42); // Compile error
Hier nutzen wir auf der rechten Seite den Diamond Operator, der es erlaubt, auf die explizite Angabe des generischen Typs zu verzichten. Das ist möglich, weil dieser aus der linken Seite der Variablendeklaration, genauer der Typangabe, hergeleitet werden kann. Wenn wir nun die Angabe von List
var mixedContent = new ArrayList<>();
mixedContent.add(Strange with var
);
mixedContent.add(42);
Kompiliert und funktioniert das? Und wenn ja, was ist daran problematisch? Tatsächlich produziert das Ganze keinen Kompilierfehler. Wie kommt das? Aufgrund des Diamond Operators bzw. der nicht vorhandenen Typangabe stehen dem Compiler nicht ausreichend Typinformationen zur Verfügung: Deswegen wird als Fallback java.lang.Object als generischer Parameter genutzt und aus der zuvor auf String typisierten Liste wird – wohl eher unerwartet – eine ArrayList
Die gezeigte Modifikation stellt potenziell einen Flüchtigkeitsfehler dar, der jedoch schwerwiegende Folgen haben kann. Genau deshalb steht der Umwandlungs-Quick-Fix in den IDEs nur dann zur Verfügung, wenn auf der linken Seite der exakte Typ angegeben wurde. In der Praxis ist dies aber durch die oftmals sinnvolle Designregel, gegen Interfaces zu programmieren, vor allem für Collections eher selten der Fall: Regelkonform arbeitet man bei der Variablendeklaration vielfach mit einem Interface statt mit einer konkreten Klasse. Bevor Sie etwas übereifrig dann selbst Hand anlegen und var nutzen, fügen Sie bitte die Typangabe auf der rechten Seite ein, um Probleme durch fehlende Typsicherheit direkt zu umgehen.
Local Variable Type Inference für Parameter von Lambdas in JDK 11
In Java 11 wurde eine minimale Anpassung bezüglich der Syntax vorgenommen: Bis einschließlich Java 10 konnte man in einem Lambda-Ausdruck bei den Parametern entweder alle Typen angeben oder alle weglassen. Die Verwendung von var war dort nicht möglich. Das wurde mit Java 11 geändert, sodass folgender Lambda-Ausdruck nun korrekt ist:
(var x, var y) -> x.doSomething(y)
Blicken wir kurz zurück: In Java 10 waren diese beiden Varianten erlaubt:
IntFunction
IntFunction
Nicht unterstützt wurde jedoch die Angabe von var:
IntFunction
Warum sollte aber diese Variante überhaupt nützlich sein, wo man doch vollständig auf die Typangabe verzichten kann? Eine berechtigte Frage! Die Antwort ergibt sich aus dem Wunsch, zwar keinen Typ explizit angeben zu müssen, jedoch etwa den Parameter trotzdem final definieren oder Annotations hinzufügen zu können. Das geht natürlich nicht, wenn die Parameterangabe vollständig ohne Typ genutzt wird. Hier erlaubt var, auf die explizite Typangabe zu verzichten und trotzdem weiter gehende Informationen bereitstellen zu können. Das wird nachfolgend durch eine Annotation @NonNull verdeutlicht:
Function
Mit dieser Anpassung in der Syntax wird mehr Konsistenz erreicht und dies ermöglicht – wie bereits im Beispiel demonstriert – die Angabe zusätzlicher Infos zu einem Parameter.
3Neues und Änderungen in JDK 9
In diesem Kapitel schauen wir uns relevante Erweiterungen aus Java 9 an, beispielsweise das Process-API. Außerdem betrachten wir die Ergänzungen im Stream-API sowie in java.util.Optional
3.1Neue und erweiterte APIs
Wie bereits angedeutet, wurden in den APIs des JDKs diverse Verbesserungen vorgenommen und Neuerungen integriert, die wir nun kennenlernen wollen. Dabei beginnen wir mit den Neuerungen im Process-API.
3.1.1Das neue Process-API
Bis einschließlich JDK 8 sind die Möglichkeiten recht eingeschränkt, wenn es darum geht, Prozesse des Betriebssystems zu kontrollieren und zu verwalten. Ein Beispiel ist die Ermittlung der ID eines Prozesses, kurz PID genannt. Je nach Plattform muss man dies mit Java 8 unterschiedlich implementieren. Das Ganze wird schnell unübersichtlich und fehlerträchtig.
PID mit JDK 9 ermitteln
Die Abfrage der PID mit Java 9 lässt sich mithilfe der Klasse java.lang.Process-Handle deutlich kürzer, besser lesbar und verständlicher als mit JDK 8 gestalten:
System.out.println(PID:
+ ProcessHandle.current().pid());
Neben den genannten Vorteilen bietet die Methode pid() einen betriebssystemunabhängigen Weg zur Ermittlung der Prozess-ID (zumindest aus Sicht des Aufrufers).
Das Interface ProcessHandle
Neben der PID kann man mithilfe von ProcessHandle noch diverse weitere Informationen zu Prozessen auslesen. Dazu gibt es unter anderem folgende Methoden:
current() – Ermittelt den aktuellen Prozess als ProcessHandle.
info() – Stellt Infos zum Prozess in Form des inneren Interface ProcessHandle.Info bereit, etwa zu Benutzer, Kommando usw.
info().command() – Gibt das Kommando als Optional
info().user() – Liefert den Benutzer als Optional
info().totalCpuDuration() – Ermittelt aus den Infos die benötigte CPU-Zeit als Optional
Zum besseren Verständnis dieser Methoden betrachten wir ein Beispiel:
public static void main(final String[] args)
{
final ProcessHandle current = ProcessHandle.current();
printInfo(current);
}
private static void printInfo(final ProcessHandle current)
{
System.out.println(PID:
+ current.pid());
System.out.println(Info:
+ current.info());
System.out.println(Command:
+ current.info().command());
System.out.println(CPU-Usage:
+ current.info().totalCpuDuration());
}
Listing 3.1 Ausführbar als ’PROCESSHANDLEEXAMPLE’
Das Programm PROCESSHANDLEEXAMPLE gibt in etwa Folgendes aus (gekürzt):
PID: 13670
Info: [user: Optional[michaeli], cmd: /Library/Java/JavaVirtualMachines/jdk
-9.jdk/Contents/Home/bin/java, args: [-Dfile.encoding=UTF-8, -Duser.country
=DE, -Duser.language=de, -Duser.variant, -cp, /Users/michaeli/Desktop/
PureJava9/quelltext/build/libs/Java9-all.jar:/Users/michaeli/Desktop/
PureJava9/quelltext/build/requiredLibs, ch3_1.processapi.
ProcessHandleExample], startTime: Optional[2017-04-06T17:21:56.927Z],
totalTime: Optional[PT0.230888S]]
Command: Optional[/Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home/
bin/java]
CPU-Usage: Optional[PT0.308141S]
Neben der PID werden diverse Informationen aus dem Info-Objekt aufgelistet, exemplarisch separat die Werte für command() und totalCpuDuration(). Für das mit command() als Optional
Alle Prozesse mit ProcessHandle abfragen
Neben Informationen zum aktuellen Prozess lassen sich Informationen für alle Prozesse des Benutzers sowie alle Subprozesse zu einem Prozess wie folgt ermitteln:
allProcesses() – Liefert alle Prozesse als Stream
children() – Ermittelt zu einem Prozess alle seine (direkten) Subprozesse als Stream
Im nachfolgenden Beispiel iterieren wir über das Ergebnis von allProcesses() und geben Infos zu solchen Prozessen aus, die Subprozesse besitzen. Die Anzahl an Subprozessen können wir durch Aufruf von children().count() erfragen:
public static void main(final String[] args)
{
System.out.println(All Processes:
);
showInfoForAllProcesses();
}
private static void showInfoForAllProcesses()
{
ProcessHandle.allProcesses().forEach(processHandle ->
{
final Stream
final long count = children.count();
if (count > 0)
{
System.out.println(Info:
+ processHandle.info() +
has
+ count + children
);
}
});
}
Listing 3.2 Ausführbar als ’ALLPROCESSHANDLESEXAMPLE’
Das Programm ALLPROCESSHANDLESEXAMPLE produziert die folgenden Ausgaben (gekürzt), die eine Liste der zurückgelieferten Informationen widerspiegeln:
All Processes:
Info: [user: Optional[michaeli], cmd: /Applications/Adobe Acrobat Reader DC.app/
Contents/MacOS/AdobeReader, args: [-psn_0_3822501], startTime: Optional
[2016-08-02T21:16:30.322Z]] has 3 children
...
Info: [user: Optional[michaeli], cmd: /System/Library/CoreServices/Dock.app/
Contents/MacOS/Dock, startTime: Optional[2016-07-24T08:17:12.938Z]] has 1
children
Info: [user: Optional[root], startTime: Optional[2016-07-24T08:16:40.564Z]] has
285 children
...
Prozesse mit ProcessHandle kontrollieren
Neben der Bereitstellung und Abfrage von Informationen zu Prozessen existieren auch verschiedene Möglichkeiten, Prozesse zu beenden sowie auf das Ende eines Prozesses zu reagieren. Dazu findet man im Interface ProcessHandle folgende Methoden:
of(long) – Liefert ein Optional
destroy() – Terminiert einen Prozess, sofern dies erlaubt ist. Ansonsten, etwa für den mit current() ermittelten Prozess, wird eine Exception ausgelöst:
Exception in thread main
java.lang.IllegalStateException: destroy of
current process not allowed
onExit() – Liefert ein CompletableFuture