Angelika Langer - Training & Consulting
HOME | COURSES | TALKS | ARTICLES | GENERICS | LAMBDAS | IOSTREAMS | ABOUT | CONTACT | Twitter | Lanyrd | Linkedin
 
HOME 

  OVERVIEW

  BY TOPIC
    JAVA
    C++

  BY COLUMN
    EFFECTIVE JAVA
    EFFECTIVE STDLIB

  BY MAGAZINE
    JAVA MAGAZIN
    JAVA SPEKTRUM
    JAVA WORLD
    JAVA SOLUTIONS
    JAVA PRO
    C++ REPORT
    CUJ
    OTHER
 

GENERICS 
LAMBDAS 
IOSTREAMS 
ABOUT 
CONTACT 
Effective Java

Effective Java
Überblick über Java 9
Teil 1: Ergänzungen an der Sprache und existierenden APIs
 

Java Magazin, Januar 2017
Klaus Kreft & Angelika Langer

Dies ist die Überarbeitung eines Manuskripts für einen Artikel, der im Rahmen einer Kolumne mit dem Titel "Effective Java" im Java Magazin erschienen ist.  Die übrigen Artikel dieser Serie sind ebenfalls verfügbar ( click here ).

 

Ergänzungen an der Sprache und existierenden APIs

Collection Literals

Für Java 7 hatte Sun damals die Java Community gefragt, welche kleineren Ergänzungen an der Programmiersprache nützlich wären.  Im Rahmen des sogenannten „Project Coin“ wurden die Vorschläge gesammelt und zum Teil für Java 7 umgesetzt.  Damals sind Features wie try-with-resources, switch-over-string und multi-catches dazu gekommen.  Es wurden aber nicht alle Vorschläge berücksichtigt.  Eine der Ideen, die liegen geblieben ist, sind die sogenannten Collection Literals.  Dabei geht es um eine kompakte Syntax für das Erzeugen von unveränderlichen Collections mit einigen wenigen Elementen.  Der damalige Vorschlag sah so aus:

 
 

List<Integer> list = #[ 1, 2, 3 ];
 
 

Für Java 9 hat man sich nun eine Lösung überlegt. Allerdings ist dafür nicht die Sprache geändert worden.  Die oben gezeigte Syntax gibt es nicht.  Aber man hat eine Lösung in der Bibliothek gefunden (siehe / COLL /).  Es wird in den Interfaces  List Set und  Map statische Factory-Methoden für das Erzeugen von unveränderlichen Collections geben.  Das sieht dann zum Beispiel so aus:
 
 

List<Integer> list = List.of( 1, 2, 3 );
 
 

Ähnliche Factory-Methoden gibt es bereits in der Klasse Collections:
 
 

// alt:

List<String> noElement    = Collections.emptyList();

List<String> oneElement   = Collections.singletonList("a");

// neu:

List<String> someElements = List.of("a", "b", "c");
 
 

Die neuen Factory-Methoden fügen sich also ganz natürlich in die bestehenden APIs ein.
 
 

Kleinere Spracherweiterungen

Es gibt eine Reihe kleinerer Spracherweiterung, die so geringfügig sind, dass man sie kaum wahrnehmen wird:

Diamond Operator an anonymen Klassen

Private Interface-Methoden

Underscore als Bezeichner

Effectively-final Variablen in try-with-resources 
 
 

Diamond Operator an Anonymen Klassen

Der sogenannte “Diamond Operator” wurde mit Java 7 eingeführt. Er erspart dem Entwickler bei der Initialisierung eines Objekts von einen parametrisierten Typ ein wenig Schreibarbeit: man darf beim Konstruktoraufruf leere spitze Klammern (eben den „Diamond Operator“) verwenden.  Das sieht so aus:

 
 

List<Long> list = new ArrayList <> ();      // fine
 
 

Leider durfte man den Diamond Operator aber nicht bei anonymen  Klassen verwenden. Das nachfolgende Beispiel hat zu einer Fehlermeldung beim Kompilieren geführt:
 
 

Callable<Long> task = new Callable <> () { // error in Java 7; fine in Java 9

   public Long call() { return 42L; }

};
 
 

Diese Fehlermeldung wird man in Java 9 nicht mehr bekommen. Der Diamond Operator ist jetzt auch bei anonymen Klassen erlaubt.

Private Interface-Methoden

Seit Java 8 gibt es in Interfaces nicht nur abstrakte Methoden ohne Implementierung, sondern auch Default- und statische Methoden mit Implementierung.  Die Default- und statischen Methoden durften in Java 8 nur  public sein, weil alles in Interfaces automatisch  public ist.  Diese Beschränkung fällt in Java 9 teilweise weg.  Statische und nicht-statische Interface-Methoden dürfen in Java 9 nun auch  private deklariert werden.  Bei den privaten Instanz-Methoden muss aber das Schlüsselwort  default weglassen, denn sie liefern sie keine Default-Funktionalität für die implementierenden Klassen, weil sie nicht vererbt werden.  Private Interface-Methoden dienen ausschließlich als Hilfsmittel für die Implementierung der anderen Methoden im Interface.

 
 

Hier eine Übersicht darüber, welche Interface-Methoden  private sein dürfen und welche nicht:
 
 

interface Interface {

  public          void abstractMethod()      ;   // since Java 1

  private         void notAllowed()          ;   // error
 
 

  public  default void defaultMethod()       {}  // since Java 8

  private default void notAllowed()          {}  // error

  private         void privateMethod()       {}  // since in Java 9
 
 

  public  static  void staticMethod()        {}  // since Java 8

  private static  void privateStaticMethod() {}  // since Java 9

}
 
 

Underscore als Bezeichner

Bis Java 7 einschließlich war der Underscore „_“ ein gültiger Name z.B. für eine Variable.  Das Folgende war also erlaubt:
 
 

void f( int  ) {

       System.out.println(  _ );

}
 
 

In Java 8 war ein solcher Bezeichner dann "deprecated".  In Java 9 ist der einzelne Underscore nun kein gültiger Bezeichner mehr und wird vom Compiler als Fehler gemeldet.  
 
 

Der genaue Grund für das Einkassieren des Underscores ist nicht bekannt.  Es wurde im Zusammenhang mit der Definition der Syntax der Lambda-Ausdrücke diskutiert, ob man den Underscore - ähnlich wie in Scala oder anderen Sprachen - auch in Java als Platzhalter für einen Namen zulassen sollte.  Die Expertengruppe hatte sich dagegen entschieden und es gibt von Brian Goetz, Oracle's Java Language Architect, die etwas nebulöse Aussage,  man habe den Underscore für mögliche sinnvolle Dinge in Java 10+ vorgesehen (siehe / SOVF /).

Effectively-final Variablen in try-with-resources

Das try-with-resourcesKonstrukt wurde mit Java 7 eingeführt. Es sieht so aus:
 
 

String readFirstLineFromFile(String path) throws IOException {

  try ( BufferedReader br 

          = new BufferedReader(new FileReader(path)) ) {

      return br.readLine();

  }

}
 
 

Vor dem eigentlich  try -Block darf ein Objekt von einem Typ erzeugt werden, der das  AutoCloseable Interface implementiert.  Der Compiler generiert dann automatisch einen  finally -Block, in dem die  close() -Methode aufgerufen wird.  Für dieses Konstrukt musste in Java 7 stets eine neue Variable deklariert werden, die der Compiler dann implizit als  final deklariert hat und die dann nur im try-with-resources-Konstrukt verwendbar war. 
 
 

In Java 9 darf man jetzt das  AutoCloseable -Objekt vor dem try-with-resources-Konstrukt erzeugen und im try-with-resources-Konstrukt eine Variable auf das Objekt verwenden.  Diese Variable muss nicht explizit als  final deklariert sein, aber der Compiler behandelt sie wie eine  final -Variable. Das heißt die Variable ist „effectively final“.  Es darf also in Java 9 so aussehen:
 
 

String readFirstLineFromFile(String path) throws IOException {

final BufferedReader br

          = new BufferedReader(new FileReader(path));

    try (br) {

      return br.readLine();

    }

}
 
 

wobei man die  final -Deklaration auch weglassen darf.

Ergänzungen zum Stream API

Interface Stream

Das Interface  Stream im Package  java.util.stream ist mit Java 8 zum JDK hinzugekommen und definiert die sogenannten Stream-Operationen.  Für Java 9 ist es ergänzt worden: es gibt zwei neue Stream-Operationen  takeWhile()   und  dropWhile() , eine Variante der Factory-Methode  iterate() mit Endekriterium und eine neue Factory-Methode  ofNullable()

 
 

Die Operationen  takeWhile()   und  dropWhile() ähneln den Operationen  limit() und  skip() .   Während man bei  limit() und  skip() angibt, wie viele Elemente verarbeitet bzw. übersprungen werden sollen, wird bei  takeWhile() und  dropWhile() eine Bedingung mitgegeben, die bestimmt, wie lange Stream-Elemente verarbeitet bzw. übersprungen werden.
 
 

Hier ein Beispiel:
 
 

IntStream.range(0,100)

         . limit(5)                               // verarbeite nur die ersten 5 Stream-Elemente

          .forEach(System.out::print );
 
 

IntStream.range(0,100)

          . takeWhile( i->i<5 )                // verarbeite die ersten Stream-Elemente , solange sie < 5 sind

          .forEach(System.out::print );
 
 

In beiden Fällen werden die Zahlen  0 1 2 3 4 ausgegeben.
 
 

Die Factory-Methode  iterate() gibt es schon seit Java 8.  Sie erzeugt einen unendlich langen Stream.  In Java 9 gibt es nun eine zweite Variante davon, bei der man eine Abbruchbedingung spezifizieren kann.
 
 

Hier ein Beispiel:
 
 

Stream. iterate(0 , i->i+1)                //  erzeuge die Zahlen 0 1 2 3 4 5 usw. ohne Ende

      .limit(5 )

       .forEach(System.out::print();

Stream. iterate(0 , i ->i< 5 , i->i+1)                //  erzeuge die Zahlen 0 1 2 3 4 5 usw., solange sie < 5 sind

       .forEach(System.out::print();
 
 

In beiden Fällen werden die Zahlen  0 1 2 3 4 ausgegeben.
 
 

Die Factory-Methode  ofNullable() ist eine reine Convenience-Methode. Sie erzeugt einen leeren Stream, wenn das Argument  null ist und sonst einen Stream mit einem Element.
 
 

Anstelle von

               (arg==null)?Stream.empty():Stream.of(arg)

kann man jetzt sagen

               Stream.ofNullable(arg)

Diese kompaktere Schreibweise verbessert im Zusammenhang mit der Stream-Operation  flatMap() die Lesbarkeit.

Klasse Collectors

In der Klasse  Collectors gibt es neue Downstream-Kollektoren für die Nachverarbeitung der Gruppen nach einem Grouping oder Partitioning, nämlich  Collectors.filtering() und  Collectors.flatMapping() .

Klasse Optional

Die  Optional -Klassen haben ebenfalls einige neue Methoden bekommen:
- stream() macht aus einem  Optional<T> einen  Stream<T> .
- or() ersetzt ein leeres  Optional durch ein anderes  Optional .
- ifPresentOrElse() akzeptiert Aktionen sowohl für das nicht-leere als auch das leere Optional; bisher gab es nur  ifPresent() .
Eine Reihe von JDK-Abstraktionen liefern in Java 9 ihre Ergebnisse als Streams und haben entsprechende neue Methoden bekommen, z.B. die Methode  date s Until() in der Klasse  java.time.LocalDate , die Methoden  tokens() und  findAll() in der Klasse  java.util.Scanner oder die Methode  results() in der Klasse  java.util.regex.Matcher .

Methode Files.lines()

Die statische Methode  Stream <String> lin es(Path path, Charset cs) in der Klasse  java.nio.file.Files ist optimiert worden. Sie liefert die Zeilen einer Datei als  Stream<String> .  Neu ist in Java 9, dass sie Memory-Mapped-I/O verwendet, um im parallelen Falle das Splitting zu beschleunigen - zumindest für die Character-Encodings UTF-8, US-ASCII and ISO-8859-1

Ergänzungen zum Process API

Die Klasse  java.lang.Process gibt es schon seit Java 1.0.  Man kann damit Prozesse auf Betriebsystemebene starten.   Mit Java 5 ist ein  ProcessBuilder hinzugekommen, der in Java 7 erweitert wurde und insbesondere das Umlenken der Input- und Output-Kanäle solcher Prozesse vereinfacht. 

 
 

Mit Java 9 kommt jetzt eine neue Abstraktion  ProcessHandle dazu, welche Zugriff und Informationen über beliebige Prozesse liefert.  Man kann sich beispielsweise eine Liste aller Prozesse auf der Maschine geben lassen und sich Information dazu holen.  Das ist anders als bei der Klasse  Process , die nur Prozesse beschreibt, die im aktuellen Prozess der JVM gestartet wurden.  Anders als beim  Process bekommt man aber beim  ProcessHandle keinen Zugriff auf die Input- und Output-Kanäle des Prozesses.
 
 

Auch die Klasse  Process selbst wurde in Java 9 erweitert: sie liefert jetzt mehr Informationen über den Prozess, wie z.B. die Liste der Child- oder Parent-Prozesse, die Liveness und allgemeine Information wie Prozess-Id, Startzeit, CPU-Zeit, Kommandozeile, etc.  
 
 

Schon in Java 8 wurde die Klasse  Process u. a. mit einer  waitFor()- Methode ausgestattet, die es erlaubt, auf die Terminierung eines Prozesses zu warten.  In Java 9 gibt es nun als Alternative zu dieser  waitFor() -Methode eine  onExit() -Methode, die ein  CompletableFuture<Process> liefert. Damit muss man nicht mehr synchron auf die Beendigung der Prozesse warten, sondern kann die asynchronen Möglichkeiten des  CompletableFuture nutzen.
 
 

Als Beispiel wollen wir mehrere Prozess-Pipelines starten und warten, bis alle Prozesse in allen Pipelines fertig sind, damit wir anschließend die Ergebnisdatei verarbeiten können, in die alle Prozesse geschrieben haben.  Das hätte man in Java 8 mit Hilfe von der  waitFor()- Methode so machen können:
 
 

List<Process> p1 = pdfFileNamesIn( dirName1 );
List<Process> p2 = pdfFileNamesIn( dirName2 );
List<Process> p3 = pdfFileNamesIn( dirName3 );

p1.addAll(p2); p1.addAll(p3);                //1
for (Process p : p1) {
    p. waitFor ();                                                                            //2

}

use ResultFile ();                                                                            //3
 
 

Wir haben alle Prozesse in eine Liste kopiert (//1), dann synchron auf das Ende eines jeden Prozesses gewartet (//2) und schließlich die Ergebnisdatei verarbeitet (//3).
 
 

In Java 9 geht es jetzt asynchron mit Hilfe der  onExit()- Methode:
 
 

List<Process> p1 = pdfFileNamesIn(dirName1);
List<Process> p2 = pdfFileNamesIn(dirName2);
List<Process> p3 = pdfFileNamesIn(dirName3);
 

CompletableFuture <?>[] processFutures
Stream. of (p1,p2,p3).flatMap(List::stream)                               //1
        .map(Process:: onExit )                                                                                                          //2
        .toArray(CompletableFuture[]:: new ) ;                               //3

CompletableFuture. allOf ( processFutures)                                                             // 4
   .thenRun(()-> use ResultFile ());                                                                                           // 5
 
 

Wenn alle Prozesse fertig sind (//4), dann soll asynchron in einem Worker-Thread die Ergebnisdatei verarbeitet werden (//5).  Auf die Terminierung aller Threads kann man mit Hilfe der Methode  CompletableFuture.allOf() reagieren.  Dafür brauchen wir ein Array mit allen Futures für alle Prozesse.  Dieses Array erzeugen wir mit Hilfe eines Streams:  wir vereinen alle Prozesse zu einem  Stream<Process> (//1), holen zu jedem Prozess ein  CompletableFuture<Process> für das Ende des Prozesses (//2) und legen alle  CompletableFuture<Process> in einem Array ab (//3), das wir anschießend an die Methode  CompletableFuture.allOf() übergeben können.
 
 

 
 

If you are interested to hear more about this and related topics you might want to check out the following seminars:
Seminars
Java Module System - Names, Unnamed, Automatic Modules, Module Descriptors, Tool Chain
1 day seminar ( open enrollment and on-site)
Java 8 & 9 - Lambdas & Stream, New Concurrency Utilities, Date/Time API, Module System
4 day seminar ( open enrollment and on-site)
 

 

  © Copyright 1995-2018 by Angelika Langer.  All Rights Reserved.    URL: < http://www.AngelikaLanger.com/Articles/EffectiveJava/91.Java9.What-is-new-in-Java-9/90.java-9.1.overview.ready_3.html  last update: 26 Oct 2018