Entdecken Sie Millionen von E-Books, Hörbüchern und vieles mehr mit einer kostenlosen Testversion

Nur $11.99/Monat nach der Testphase. Jederzeit kündbar.

Java – die Neuerungen in Version 9 bis 14: Modularisierung, Syntax- und API-Erweiterungen
Java – die Neuerungen in Version 9 bis 14: Modularisierung, Syntax- und API-Erweiterungen
Java – die Neuerungen in Version 9 bis 14: Modularisierung, Syntax- und API-Erweiterungen
eBook748 Seiten4 Stunden

Java – die Neuerungen in Version 9 bis 14: Modularisierung, Syntax- und API-Erweiterungen

Bewertung: 0 von 5 Sternen

()

Vorschau lesen

Über dieses E-Book

Bleiben Sie bei Java auf dem Laufenden!
  • 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.


SpracheDeutsch
Herausgeberdpunkt.verlag
Erscheinungsdatum5. Mai 2020
ISBN9783960889793
Java – die Neuerungen in Version 9 bis 14: Modularisierung, Syntax- und API-Erweiterungen

Mehr von Michael Inden lesen

Ähnlich wie Java – die Neuerungen in Version 9 bis 14

Ähnliche E-Books

Programmieren für Sie

Mehr anzeigen

Ähnliche Artikel

Rezensionen für Java – die Neuerungen in Version 9 bis 14

Bewertung: 0 von 5 Sternen
0 Bewertungen

0 Bewertungen0 Rezensionen

Wie hat es Ihnen gefallen?

Zum Bewerten, tippen

Die Rezension muss mindestens 10 Wörter umfassen

    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, wurde um Funktionalität erweitert. Neben Vereinfachungen beim Prozess-Handling, der Verarbeitung mit Optional oder von Daten mit InputStreams schauen wir auf fundamentale Neuerungen im Bereich der Concurrency durch Reactive Streams. Darüber hinaus enthalten Java 10 und 11 eine Vielzahl kleinerer weiterer Neuerungen. Eine größere Änderung ist der mit Java 11 offiziell ins JDK 11 aufgenommene HTTP/2-Support. In Java 12 wurden ein paar API-Erweiterungen integriert. Mit Java 13 finden wir zwei Previews auf Syntaxänderungen, nämlich einmal bezüglich switch und zudem die sogenannten »Text Blocks«, die mehrzeilige Strings erlauben. Mit Java 14 werden die Neuerungen bei switch endlich in den Sprachstandard aufgenommen. Außerdem finden wir eine hilfreiche Neuerung zur Fehleranalyse bei NullPointerExceptions. Zusätzlich bietet Java 14 zwei Erweiterungen bei der Definition mehrzeiliger Texte. Ganz besonders interessant sind sogenannte Records, die eine extrem kompakte Schreibweise zum Deklarieren spezieller Klassen mit unveränderlichen Daten ermöglichen.

    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 byLengthJdk8 = new Comparator<String>()

    {

    ...

    };

    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 byLength = new 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, Character> firstChar =

    entry -> entry.getKey().charAt(0);

    Predicate> isAdult = entry -> entry.getValue() >= 18;

    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, Character> firstChar =

    entry -> entry.getKey().charAt(0);

    Predicate> isAdult = entry -> entry.getValue() >= 18;

    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>) entry -> entry.getValue() >= 18;

    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 ist, sondern vom Typ ArrayList:

    // var => ArrayList

    var names = new ArrayList();

    names = new LinkedList(); // Compile Error

    Versucht man, eine Instanz einer LinkedList an die Variable names zuzuweisen, so erzeugt dies die Fehlermeldung: »type mismatch: cannot convert from LinkedList to ArrayList«. Der Grund sollte Ihnen mittlerweile bekannt sein: Es kommt zum Fehler, weil der durch var repräsentierte Typ ArrayList und eben nicht List ist, wie man es vielleicht erwarten würde.

    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 = new ArrayList<>();

    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 durch var ersetzen und das Kommentarzeichen vor dem zweiten Aufruf von add() löschen:

    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 doubleItTyped = (final int x) -> x * 2;

    IntFunction doubleItNoType = x -> x * 2;

    Nicht unterstützt wurde jedoch die Angabe von var:

    IntFunction doubleItWithVar = (var x) -> x * 2;

    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 trimmer = (@NonNull var str) -> str.trim();

    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, aber auch die Collection-Factory-Methoden. Darüber hinaus findet man Ergänzungen in der Klasse java.io.InputStream sowie diverse Neuerungen, unter anderem im Bereich Concurrency: Zum einen bietet die Klasse java.util.concurrent.CompletableFuture mehr Funktionalität. Zum anderen finden wir die Unterstützung von Reactive Streams durch die im Package java.util.concurrent eingeführte Klasse Flow. Außerdem wurden Utility-Klassen wie java.util.Objects und java.util.Arrays sinnvoll ergänzt.

    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 aus einem ProcessHandle.Info zurück.

    info().user() – Liefert den Benutzer als Optional aus einem ProcessHandle.Info.

    info().totalCpuDuration() – Ermittelt aus den Infos die benötigte CPU-Zeit als Optional. Die Klasse java.time.Duration entstammt dem mit JDK 8 neu eingeführten Date and Time API (vgl. Anhang A).

    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 ermittelte Kommando wird deutlich, dass es sich um das Programm java aus JDK 9 handelt, das laut totalCpuDuration() etwa 0.31 Sekunden CPU-Zeit verbraucht hat, wie man anhand von Optional sieht.

    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 children = processHandle.children();

    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 zu einer gegebenen PID.

    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 zurück, das man dazu nutzen kann, verschiedene

    Gefällt Ihnen die Vorschau?
    Seite 1 von 1