Java Inseln bald in den Formaten PDF, EPUB und MOBI

Der Verlag hat lange an der digitalen Umsetzung für elektronische Lesegeräte gearbeitet. Die Inseln (und auch alle anderen Galileo-Bücher) wird es ab Ende Mai (also zur neuen Ausgabe für Java 8) als E-Books geben, und zwar in den Formaten PDF, EPUB und Kindle MOBI. Man wird dann das E-Book kaufen können (im Kauf sind alle Formate enthalten) oder man kauft die Print-Ausgabe und damit auch das E-Book (auch alle Formate). Wer bis dahin nicht warten kann muss das das Buch ganz normal herunterladen und mit (freien) Tools in das gewünschte Format konvertieren.

Methoden-Referenz von Java 8

Je größer Software-Systeme werden, desto wichtiger werden Dinge wie Klarheit, Wiederverwendbarkeit und Dokumentation. Wir haben für unseren String-Comparator eine Implementierung geschrieben, anfangs über eine innere Klasse, später über einen Lambda-Ausdruck. In jedem Fall haben wir Code geschrieben. Doch was wäre, wenn eine Utility-Klasse schon eine Implementierung mitbringen würde? Dann könnte der Lambda-Ausdruck natürlich an die vorhandene Implementierung delegieren, und wir sparen Code. Schauen wir uns das mal an einem Beispiel an:

class StringUtils {
  public static int compareTrimmed( String s1, String s2 ) {
    return s1.trim().compareTo( s2.trim() );
  }    
}

public class CompareIgnoreCase {
  public static void main( String[] args ) {
    String[] words = { "A", "B", "a" };
      Arrays.sort( words, (String s1, String s2) -> 
StringUtils.compareTrimmed(s1, s2) );
      System.out.println( Arrays.toString( words ) );
  }
}

Auffällig ist hier, dass die referenzierte Methode compareTrimmed(String,String) von den Parametertypen und vom Rückgabetyp genau auf die compare(…)-Methode eines Comparator passt. Für genau solche Fälle gibt es eine weitere syntaktische Verkürzung, so dass im Code kein Lambda-Ausdruck, sondern nur noch ein Methodenverweis notwendig ist.

Definition: Eine Methoden-Referenz ist  ein Verweis auf  eine Methode ohne diese jedoch aufzurufen. Syntaktisch trennen zwei Doppelpunkte den Klassenamen bzw. die Referenz auf der linken Seite von dem Methodennamen auf der rechten.

Die Zeile

Arrays.sort( words, (String s1, String s2) -> StringUtils.compareTrimmed(s1, s2) );

lässt sich mit einer Methoden-Referenzen abkürzen zu:

Arrays.sort( words, StringUtils::compareTrimmed );

Die Sortiermethode erwartet vom Comparator eine Methode, die zwei Strings annimmt und eine Ganzzahl zurückgibt. Der Name der Klasse und der Name der Methode sind unerheblich, weshalb an dieser Stelle eine Methoden-Referenz eingesetzt werden kann.

Eine Methoden-Referenz ist wie ein Lambda-Ausdruck ein Exemplar einer funktionalen Schnittstelle, jedoch für eine existierende Methode einer bekannten Klasse. Wie üblich bestimmt der Kontext von welchem Typ genau der Ausdruck ist.

Hinweis: Gleicher Code für eine Methoden-Referenz kann zu komplett unterschiedlichen Typen führen – der Kontext macht den Unterschied:

Comparator<String>                  c1 = StringUtils::compareTrimmed;
BiFunction<String, String, Integer> c2 = StringUtils::compareTrimmed;

Varianten von Methoden-Referenzen

Im Beispiel ist die Methode compareTrimmed(…) statisch, und links vom Doppelpunkt steht der Name eines Typs. Allerdings kann beim Einsatz eines Typnamen die Methode auch nicht-statisch sein, String::length ist so ein Beispiel. Das wäre eine Funktion, die ein String auf ein int abbildet, in Code: Function<String, Integer> len = String::length;.

Links von den zwei Doppelpunkten kann auch eine Referenz stehen, was dann immer eine Objektmethode referenziert.

Beispiel: Während String::length eine Funktion ist, wäre string::length ein Supplier, unter der Annahme, das string eine Referenzvariable ist:

String string = "Goll";
Supplier<Integer> len = string::length;
System.out.println( len.get() );     // 4

System.out ist eine Referenz und eine Methode wie println(…) kann an einen Consumer gebunden werden. Es ist aber auch ein Runnable, weil es println() auch ohne Parameterliste gibt.

Consumer<String> out = System.out::println;
out.accept( "Kates kurze Kleider" );
Runnable out = System.out::println;
out.run();

Ist eine Hauptmethode mit main(String… args) deklariert, so ist das auch ein Runnable:

Runnable r = JavaApplication1::main;

Anderes wäre das bei main(String[]), hier ist ein Parameter zwingend, doch ein Vararg kann auch leer sein.

Statt dass der Name einer Referenzvariablen gewählt wird, kann auch this das Objekt beschreiben und auch super ist möglich. this ist praktisch, wenn die Implementierung einer funktionalen Schnittstelle auf eine Methode der eigenen Klasse delegieren möchte. Wenn zum Beispiel eine lokale Methode compareTrimmed(…) in der Klassen existieren würde, in der auch der Lambda-Ausdruck steht,  und sollte diese Methode als Comparator in Arrays.sort(…) verwendet werden, könnte es heißen: Arrays.sort(words, this::compareTrimmed).

Hinweis: Es ist nicht möglich eine spezielle Methode über die Methodenreferenz auszuwählen. Eine Angabe wie String::valueOf oder Arrays::sort ist relativ breit – bei letzterem wählt der Compiler eine der 18 passenden überladen Methoden aus. Da kann es passieren, dass der Compiler eine falsche Methode auswählt, in dem Fall muss ein expliziter Lambda-Ausdruck eine Mehrdeutigkeit auflösen. Bei generischen Typen kann zum Beispiel List<String>::length oder auch List::length stehen auch hier erkennt der Compiler wieder alles selbst.

Was soll das alles?

Einem Einsteiger in die Sprache Java wird dieses Sprache-Feature wie der größte Zauber auf Erden vorkommen, und auch Java-Profis bekommen hier zittrige Finger, entweder vor Furcht oder Aufregung… In der Vergangenheit musste in Java sehr viel Code explizit geschrieben werden, aber mit diesen neuen Methoden-Referenzen erkennt und macht der Compiler vieles von selbst.

Nützlich wird diese Eigenschaft mit den funktionalen Bibliotheken aus Java 8, die ein eigenes Kapitel einnehmen. Hier nur ein kurzer Vorgeschmack:

Object[] words = { " ", '3', null, "2", 1, "" };
Arrays.stream( words )
      .filter( Objects::nonNull )
      .map( Objects::toString )
      .map( String::trim )
      .filter( s -> ! s.isEmpty() )
      .map( Integer::parseInt )
      .sorted()
      .forEach( System.out::println );   // 1 2 3

Klassen mit einer abstrakten Methode als funktionale Schnittstelle?

Als die Entwickler der Sprache Java die Lambda-Ausdrücke diskutierten, stand auch die Frage im Raum, ob abstrakte Klassen, die nur über eine abstrakte Methode verfügen, ebenfalls für Lambda-Ausdrücke genutzt werden können.[1] Sie entschieden sich dagegen, unter anderem deswegen, weil bei der Implementierung von Schnittstellen die JVM weitreichende Optimierungen vornehmen kann. Und bei Klassen wird das schwierig. Das liegt auch daran, dass ein Konstruktor umfangreiche Initialisierungen mit Seiteneffekten vornimmt (die Konstruktoren aller Oberklassen nicht zu vergessen), sowie Ausnahmen auslösen könnte. Gewünscht ist aber nur die Ausführung einer Implementierung der funktionalen Schnittstelle und kein anderer Code.

Es gibt nun im JDK einige abstrakte Klassen, die genau eine abstrakte Methode vorschreiben, etwa java.util.TimerTask. Solche Klassen können nicht über einen Lambda-Ausdruck realisiert werden; hier müssen Entwickler weiterhin zu Klassenimplementierungen greifen, und die kürzeste Lösung ist eine innere anonyme Klasse. Eigene Hilfsklassen können natürlich den Code etwas abkürzen, aber eben nur mit Hilfe einer eigenen Implementierung.

Wer abstrakte Methoden mit Lambda-Ausdrücken implementieren möchte, kann mit Hilfsklassen arbeiten. Denn wenn eine Hilfsklasse funktionale Schnittstellen einsetzt, so können Lambda-Ausdrücke wieder ins Spiel kommen, in dem die Implementierung der abstrakten Methode an den Lambda-Ausdruck weiterleitet. Nehmen wir das Beispiel für TimerTask und gehen zwei unterschiedliche Strategien der Implementierung durch. Mit Delegation sieht das so aus:

import java.util.*;

class TimerTaskLambda {

  public static TimerTask createTimerTask( Runnable runnable ) {
    return new TimerTask() {
        @Override public void run() { runnable.run(); }
    };
  }
 
  public static void main( String[] args ) {
    new Timer().schedule( createTimerTask( () -> System.out.println("Hi") ), 500 );
  }
}

Mit Vererbung erhalten wir:

public class LambdaTimerTask extends TimerTask {
  private final Runnable runnable;
    public LambdaTimerTask( Runnable runnable ) {
    this.runnable = runnable;
  }
   
  @Override public void run() { runnable.run(); }
}

Der Aufruf erfolgt dann statt createTimerTask(…) mit dem Konstruktor:

new Timer().schedule( new LambdaTimerTask( () -> System.out.println("Hi") ), 500 );

[1]              Früher wurde hier die Abkürzung SAM (Single Abstract Method) genutzt.

Ausnahmen in Lambda-Ausdrücken

Lambda-Ausdrücke sind Implementierung von funktionalen Schnittstellen, und bisher haben wir noch nicht die Frage betrachtet was passiert, wenn der Code-Block vom Lambda-Ausdruck eine Ausnahme auslöst und wer diese Auffangen muss.

Ausnahmen im Code-Block eines Lambda-Ausdrucks

In java.util.function gibt es eine funktionale Schnittstelle Predicate, dessen Deklaration im Kern wie folgt ist:

public interface Predicate<T> { boolean test( T t ); }

Ein Predicate führt einen Test durch und liefert wahr oder falsch als Ergebnis. Ein Lambda-Ausdruck kann diese Schnittstelle nun implementieren. Nehmen wir an, wir wollen Testen, ob eine Datei die Länge 0 hat, um etwa Datei-Leichen zu finden. In einer ersten Idee greifen wir auf die existierende Files-Klasse zurück, die size(…)anbietet:

Predicate<Path> isEmptyFile = path -> Files.size( path ) == 0;  // N Compilerfehler

Problem dabei ist, das Files.size(…) eine IOException auslöst, die behandelt werden muss und zwar nicht vom Block, in dem der Lambda-Ausdruck als Ganzes steht, sondern vom Code im Lambda-Ausdruck selbst. Das schreibt der Compiler so vor. Folgendes ist also keine Lösung:

try {
  Predicate<Path> isEmptyFile = path -> Files.size( path ) == 0; // Nee
} catch ( IOException e ) { … }

sondern nur:

Predicate<Path> isEmptyFile = path -> {
  try {
    return Files.size( path ) == 0;
  } catch ( IOException e ) { return false; }
};

Die Eigenschaft, die Java fehlt, nennt sich Exception-Transparenz, und hier ist deutlich der Unterschied zwischen geprüften und ungeprüften Ausnahmen zu sehen. Bei der Exception-Transparenz wäre kein Ausnahmebehandlung im Lambda-Ausdruck nötig und an einer übergeordneten Stelle möglich. Doch da diese Möglichkeit in Java fehlt, bleibt uns nur übrig, geprüfte Ausnahmen im Lambda-Ausdrücken direkt zu behandeln.

Funktionale Schnittstellen mit throws-Klausel

Ungeprüfte Ausnahmen können immer auftreten und führen (nicht abgefangen) wie üblich zum Abbruch des Threads. Eine throws-Klausel an den Methoden/Konstruktoren ist dafür nicht nötig. Doch können Funktionale Schnittstellen eine throws-Klausel mit geprüften Ausnahmen deklarieren, und die Implementierung einer funktionalen Schnittstelle kann logischerweise geprüfte Ausnahmen auslösen.

Eine Deklaration wie Callable aus dem Paket java.util.concurrent macht das deutlich. (Callable trägt kein @FunctionalInterface):

public interface Callable<V> {
  V call() throws Exception;
}

Das könnte durch folgenden Lambda-Ausdruck realisiert werden:

Callable<Integer> randomDice = () -> (int)(Math.random() * 6) + 1;

Der Aufruf von call() auf einem randomDice muss mit einer Ausnahmebehandlung einher gehen, da call() eine Exception auslöst, etwa so:

try {
  System.out.println( randomDice.call() );
  System.out.println( randomDice.call() );
}
catch ( Exception e ) { … }

Dass der Aufrufer die Ausnahme behandeln muss ist klar. Die Deklaration des Lambda-Ausdrucks enthält keinen Hinweis auf die Ausnahme, das ist ein Unterschied zum vorangegangenen Abschnitt.

Design-Tipp

Ausnahmen in Methoden funktionaler Schnittstellen schränken den Nutzen stark ein, und daher löst keine der funktionalen Schnittstellen aus etwa java.util.function eine geprüfte Ausnahme aus. Der Grund ist einfach, denn jeder Methodenaufrufer müsste sonst entweder die Ausnahme weiterleiten oder behandeln.[1]

Um die Einschränkungen und Probleme mit einer throws-Klausel  noch etwas deutlicher zu machen stellen wir uns vor, dass die funktionale Schnittstelle Predicate ein throws Exception (vom Sinn der Typs Exception an sich einmal abgesehen) enthält:

interface Predicate<T> { boolean test( T t ) throws Exception; } // Was wäre wenn?

Die Konsequenz wäre, dass jeder Aurufer von test(…) nun seinerseits die Exception in die Hände bekommt und sie auffangen oder weiterleiten muss. Leitet der test(….)-Aufrufer mit throws Exception die Ausnahme weiter nach oben, bekommen wir plötzlich an allen Stellen ein throws Exception in die Methodensignatur, was auf keinen Fall gewünscht ist. So enthält jetzt etwa ArrayList eine Deklaration von removeIf(Predicate filter); hier müsste dann removeIf(…) – was letztendlich filter.test(…) aufruft – sich mit der Test-Ausnahme rumärgern und removeIf(Predicate filter) throws Exception ist keine gute Sache.

Von geprüft nach ungeprüft

Geprüfte Ausnahmen sind in Lamba-Ausdrücken nicht schön. Eine Lösung ist, Code, der geprüfte Ausnahmen auslöst, zu verpacken und die geprüfte Ausnahme in einer ungeprüften zu manteln. Das kann etwa so aussehen:

public class PredicateWithException {

 @FunctionalInterface
 public interface ExceptionalPredicate<T, E extends Exception> {
   boolean test( T t ) throws E;
 }

 public static <T> Predicate<T> asUncheckedPredicate( ExceptionalPredicate<T, Exception> predicate ) {
  return t -> {
   try {
    return predicate.test( t );
   }
   catch ( Exception e ) {
    throw new RuntimeException( e.getMessage(), e );
   }
  };
 }
 public static void main( String[] args ) {
  Predicate<Path> isEmptyFile = asUncheckedPredicate( path -> Files.size( path ) == 0 );
  System.out.println( isEmptyFile.test( Paths.get( "c:/" ) ) );
 }
}

Die Schnittstelle ExceptionalPredicate ist ein Prädikat mit optionaler Ausnahme. In der eigenen Hilfsmethode asUncheckedPredicate(ExceptionalPredicate) nehmen wir so ein ExceptionalPredicate an und packen es in ein Predicate, was die Methode zurückgibt. Geprüfte Ausnahmen werden in eine ungeprüfte Ausnahme vom Typ RuntimeException gesetzt. Somit muss Predicate keine geprüfte Ausnahme weiterleiten, was es ja laut Deklaration auch nicht kann.


[1]       Von Callable gibt es zwar Nutzer, die mit Nebenläufigkeit (daher das Paket java.util.concurrent) in Zusammenhang stehen, aber keine weiteren Verwendungen in der Java-Bibliothek, von zwei Beispielen aus javax.tools abgesehen. Mit java.util.function.Supplier existiert eine entsprechende Alternative ohne throws-Klausel.

Die Umgebung der Lambda-Ausdrücke und Variablenzugriffe

Ein Lambda-Ausdruck „sieht“ seine Umgebung genauso wie der Code, der vor oder nach dem Lambda-Ausdruck steht. Insbesondere hat ein Lambda-Ausdruck vollständigen Zugriff auf alle Eigenschaften der Klasse, genauso wie auch der einschließende äußere Block sie hat. Es gibt keinen besonderen Namensraum, sondern nur neue und vielleicht überdeckte Variablen durch die Parameter. Das ist einer der grundlegenden Unterschiede zwischen Lambda-Ausdrücken und inneren Klassen. Somit ist auch die Bedeutung von this und super bei Lambda-Ausdrücken und inneren Klassen unterschiedlich.

Zugriff auf finale, lokale Variablen/Parametervariablen

Lambda-Ausdrücke können problemlos auf Objektvariablen und Klassenvariablen lesend und schreibend zugreifen. Auch auf lokale Variablen und Parameter hat ein Lambda-Ausdruck Zugriff. Doch greift ein Lambda-Ausdruck auf lokale Variablen bzw. Parametervariablen zu, müssen diese final sein. Dass eine Variable final ist, muss nicht extra mit einem Modifizierer geschrieben werden, aber sie muss effektiv final (engl. effectively final) sein. Effektiv final ist eine Variable, wenn sie nach der Initialisierung nicht mehr beschrieben wird.

Ein Beispiel: Der Benutzer soll über eine Eingabe die Möglichkeit bekommen zu bestimmen, ob String-Vergleiche mit unserem trimmenden Comparator unabhängig von der Groß-/Kleinschreibung stattfinden sollen.

public class CompareIgnoreCase {
  public static void main( String[] args ) {
    /*final*/ boolean compareIgnoreCase = new Scanner( System.in ).nextBoolean();
    Comparator<String> c = (s1, s2) -> compareIgnoreCase ?
           s1.trim().compareToIgnoreCase( s2.trim() ) :
           s1.trim().compareTo( s2.trim() );
    String[] words = { "M", "\nSkyfall", " Q", "\t\tAdele\t" };
    Arrays.sort( words, c );
    System.out.println( Arrays.toString( words ) );
  }
}

Ob compareIgnoreCase von uns final gesetzt wird oder nicht ist egal, denn die Variable wird hier effektiv final verwendet. Natürlich kann es nicht schaden, final als Modifizierer immer davor zu setzen, um dem Leser des Codes diese Tatsache bewusst zu machen.

Neu eingeschobene Lambda-Ausdrücke, die auf lokale Variablen bzw. Parametervariablen zugreifen, können also im Nachhinein zu Compilerfehlern führen. Folgendes Segment ist ohne Lambda-Ausdruck korrekt:

/*1*/ boolean compareIgnoreCase = new Scanner( System.in ).nextBoolean();
/*2*/ …
/*3*/ compareIgnoreCase = true;

Schiebt sich zwischen Zeile 1 und 3 nachträglich ein Lambda-Ausdruck rein, der auf compareIgnoreCase zugreift, gibt es anschließend einen Compilerfehler. Allerdings liegt der Fehler nicht in Zeile 3, sondern beim Lambda-Ausdruck. Denn die Variable compareIgnoreCase ist nach der Änderung nicht mehr effektiv final, was sie aber sein müsste, um in dem Lambda-Ausdruck verwendet zu werden.

Tipp

Lambda-Ausdrücke verhalten sich genauso wie innere anonyme Klassen, die auch nur auf finale Variablen zugreifen können. Mit Behältern, wie einem Feld oder den speziellen AtomicXXX-Klassen aus dem java.util.concurrent.atomic-Paket, lässt sich das Problem im Prinzip lösen. Denn greift ein Lambda-Ausdruck etwa auf das Feld boolean[] compareIgnoreCase = new boolean[1]; zu, so ist die Variable compareIgnoreCase selbst final, aber compareIgnoreCase[0] = true; ist erlaubt und ein Schreibzugriff auf das Feld, nicht der Variablen. Je nach Code besteht jedoch die Gefahr, dass Lambda-Ausdrücke parallel ausgeführt werden. Wird etwa ein Lambda-Ausdruck mit Veränderung auf diesem Feldinhalt parallel ausgeführt, so ist der Zugriff nicht synchronisiert und das Ergebnis kann „kaputt“ sein, denn paralleler Zugriff auf Variablen muss immer koordiniert vorgenommen werden.

Namensräume

Deklariert eine innere anonyme Klasse Variablen innerhalb der Methode, so sind diese immer „neu“, das heißt, die neuen Variablen überlagern vorhandene lokale Variablen aus dem äußeren Kontext. Die Variable compareIgnoreCase kann im Rumpf von compare(…) zum Beispiel problemlos neu deklariert werden:

boolean compareIgnoreCase = true;
Comparator<String> c = new Comparator<String>() {
  @Override public int compare( String s1, String s2 ) {
   boolean compareIgnoreCase = false;        // völlig ok
   return …
  }
};

In einem Lambda-Ausdruck ist das nicht möglich, und folgendes führt zu einer Fehlermeldung des Compilers: „variable compareIgnoreCase ist already defined“.

boolean compareIgnoreCase = true;
Comparator<String> c = (s1, s2) -> {
  boolean compareIgnoreCase = false;  // N Compilerfehler
  return …
}

this-Referenz

Ein Lambda-Ausdruck unterscheidet sich von einer inneren (anonymen) Klasse auch in dem, worauf die this-Referenz verweist:

  • Beim Lambda-Ausdruck zeigt this immer auf das Objekt, in dem der Lambda-Ausdruck eingebettet ist.
  • Bei einer inneren Klasse referenziert this die innere Klasse, und die ist ein komplett neuer Typ.

Folgendes Beispiel macht das deutlich:

class InnerVsLambdaThis {
  InnerVsLambdaThis() {
    Runnable lambdaRun = () -> System.out.println( this.getClass().getName() );
    Runnable innerRun  = new Runnable() {
      @Override public void run() { System.out.println( this.getClass().getName()); }
    };
    lambdaRun.run();      // InnerVsLambdaThis
    innerRun.run();       // InnerVsLambdaThis$1
  }
 
  public static void main( String[] args ) {
    new InnerVsLambdaThis();
  }
}

Als erstes nutzen wir this in einen Lambda-Ausdruck im Konstruktor der Klasse InnerVsLambdaThis. Damit bezieht sich this auf jedes gebaute InnerVsLambdaThis-Objekt. Bei der inneren Klasse referenziert this ein anderes Exemplar und zwar vom Typ Runnable. Da es bei anonymen Kassen keinen Namen hat, trägt es lediglich die Kennung InnerVsLambdaThis$1.

Rekursive Lambda-Ausdrücke

Lambda-Ausdrücke können auf sich selbst verweisen. Da aber ein this zur Selbstreferenz nicht funktioniert, ist ein kleiner Umweg nötig. Erst muss eine Objekt- oder eine Klassenvariable deklariert werden, dann muss dieser Variablen ein Lambda-Ausdruck zugewiesen werden, und dann kann der Lambda-Ausdruck auf diese Variable zugreifen und einen rekursiven Aufruf starten. Für den Klassiker der Fakultät sieht das so aus:

public class RecursiveFactLambda {
  public static IntFunction<Integer> fact = n -> (n == 0) ? 1 : n * fact.apply(n-1);
  public static void main( String[] args ) {
    System.out.println( fact.apply( 5 ) );   // 120
  }
}

IntFunction ist eine funktionale Schnittstelle aus dem Paket java.util.function mit einer Operation T apply(int i). T ist ein generischer Rückgabetyp, den wir hier mit Integer belegt haben.

fact hätte genauso gut als normale Methode deklariert werden können. Großartige Vorteile bietet die Schreibweise mit Lambda-Ausdrücken hier nicht. Zumal jetzt auch der Begriff „anonyme Methode“ nicht mehr so richtig passt, da der Lambda-Ausdruck ja doch einen Namen hat, nämlich fact. Und weil der Lambda-Ausdruck einer Variablen zugewiesen wurde, kann er in dieser Form natürlich auch nicht mehr als Implementierung an eine Methode oder einen Konstruktor übergeben werden, sondern nur als Methoden/Konstruktor-Referenz, dazu später mehr.

Syntax für Lambda-Ausdrücke

Lambda-Ausdrücke haben wie Methoden mögliche Parameter- und Rückgabe-Werte. Die Java-Grammatik für die Schreibweise von Lambda-Ausdrücken sieht ein paar nützliche syntaktische Abkürzungen vor.

Ausführliche Schreibweise

Lambda-Ausdrücke lassen sich auf unterschiedliche Arten und Weisen schreiben, da es für diverse Konstruktionen Abkürzungen gibt. Eine Form, die jedoch immer gilt ist:

‚(‚ LambdaParameter ‚)‘ ‚->‘ ‚{‚ Anweisungen ‚}‘

Der Lambda-Parameter besteht (voll ausgeschrieben) wie ein Methodenparameter aus a) dem Typ, b) dem Namen und c) optionalen Modifizieren.

Der Parametername öffnet einen neuen Gültigkeitsbereich für eine Variable, wobei der Parametername keine anderen Namen von lokalen Variablen überlagern darf. Hier verhält sich die Lambda-Parametervariable wie eine neue Variable aus einem inneren Block und nicht wie eine Variable aus einer inneren Klasse, wo die Sichtbarkeit anders ist.

Beispiel

Folgendes gibt einen Compilerfehler im Lambda-Ausdruck, weil var schon deklariert ist, die Parametervariable vom Lambda-Ausdruck muss also „frisch“ sein:

String var = "";
var.chars().forEach( var -> { System.out.println( var ); } );  //  Compilerfehler

Abkürzung 1: Typinferenz

Der Java-Compiler kann viele Typen aus dem Kontext ablesen, was Typ-Inferenz genannt wird. Wir kennen so etwas vom Diamant-Operator, wenn wir etwa schreiben List<String> list = new ArrayList<>().

Sind für den Compiler genug Typ-Informationen verfügbar, dann erlaubt der Compiler bei Lambda-Ausdrücken eine Abkürzung. Bei

Comparator<String> c =
  (String s1, String s2) -> { return s1.trim().compareTo( s2.trim() ); };

ist Typ-Inferenz einfach (Comparator<String> sagt alles aus), daher funktioniert die folgende  Abkürzung:

Comparator<String> c = (s1, s2) -> { return s1.trim().compareTo( s2.trim() ); };

Die Parameterliste enthält also entweder explizit deklarierte Parametertypen oder implizite inferred-Typen. Eine Mischung ist nicht erlaubt, der Compiler blockt so etwas wie (String s1, s2) oder (s1, String s2) mit einem Fehler ab.

Wenn der Compiler die Typen ablesen kann, sind die Parametertypen optional. Aber Typ-Inferenz ist nicht immer möglich, weshalb die Abkürzung nicht immer möglich ist. Außerdem hilft die explizite Schreibweise auch der Lesbarkeit: kurze Ausdrücke sind nicht unbedingt die verständlichsten.

Hinweis

Der Compiler liest aus den Typen ab, ob alle Eigenschaften vorhanden sind. Die Typen sind dabei entweder explizit oder implizit gegeben.

Comparator<String> sc = (a, b) -> { return Integer.compare( a.length(), b.length() ); };
Comparator<BitSet> bc = (a, b) -> { return Integer.compare( a.length(), b.length() ); };

Die Klassen String und BitSet besitzen beide die Methode length(), daher ist der Lambda-Ausdruck korrekt. Der gleiche Lambda-Code lässt sich für zwei völlig verschiedene Klassen einsetzen, die überhaupt keine Gemeinsamkeiten haben, nur das sie zufällig beide eine Methode namens length() besitzen.

Abkürzung 2: Lambda-Rumpf ist entweder einzelner Ausdruck oder Block

Besteht der Rumpf eines Lambda-Ausdrucks nur aus einem einzelnen Ausdruck, kann eine verkürzte Schreibweise die Block-Klammern und das Semikolon einsparen. Statt

( LambdaParameter ) -> { return Ausdruck; }

heißt es dann

( LambdaParameter ) -> Ausdruck

Lambda-Ausdrücke mit einer return–Anweisung im Rumpf kommen häufig vor, da dies den typischen Funktionen entspricht. Somit ist es eine willkommene Verkürzung, wenn die abgekürzte Syntax für Lambda-Ausdrücke lediglich den Ausdruck fordert, der dann die Rückgabe bildet.

Hier sind drei Beispiele:

Lange Schreibweise Abkürzung
(s1, s2) ->

{ return s1.trim().compareTo( s2.trim() ); }

(s1, s2) ->

s1.trim().compareTo( s2.trim() )

(a, b) -> { return a + b; } (a, b) -> a + b
() -> { System.out.println(); } () -> System.out.println()

Ausführliche und abgekürzte Schreibweise

Ausdrücke können in Java auch zu void ausgewertet werden, sodass ohne Probleme ein Aufruf wie  System.out.println() in der kompakten Schreibweise ohne Block gesetzt werden kann. Das heißt, wenn Lambda-Ausdrücke mit der kurzen Ausdrucks-Syntax eingesetzt werden, können diese Ausdrücke etwas zurückgeben, müssen aber nicht.

Hinweis

Die Schreibweise mit den geschweiften Klammern und den Rückgabe-Ausdrücken kann nicht gemischt werden. Entweder gibt es ein Block geschweifter Klammern und return oder keine Klammern und kein return-Schlüsselwort. Fehler ergeben also diese falschen Mischungen:

Comparator<String> c;
c = (s1, s2) -> { s1.trim().compareTo( s2.trim() ) };    // Compilerfehler (1)
c = (s1, s2) -> return s1.trim().compareTo( s2.trim() ); // Compilerfehler (2)

Würden wir in (1) ein explizites return nutzen wäre alles in Ordnung, würde bei (2) das return wegfallen wäre die Zeile auch compilierbar.

Ob Lambda-Ausdrücke eine Rückgabe haben, drücken zwei Begriffe aus:

  • void-kompatibel: Der Lambda-Rumpf gibt kein Ergebnis zurück. Entweder weil der Block kein return enthält, oder ein return ohne Rückgabe, oder weil ein void-Ausdruck in der verkürzten Schreibweise eingesetzt wird. Der Lambda-Ausdruck () -> System.out.println() ist also void-kompatibel, genauso wie () -> {}.
  • Wert-kompatibel: Der Rumpf beendet den Lambda-Ausdruck mit einer return-Anweisung, die einen Wert zurückgibt oder besteht aus der kompakten Schreibenweise mit einer Rückgabe ungleich void.

Eine Mischung aus void- und Wert-kompatibel ist nicht erlaubt und führt wie bei Methoden zu einem Compilerfehler.[1]

Abkürzung 3: Einzelner Identifizierer statt Parameterliste und Klammern

Besteht die Parameterliste

a) nur aus einem einzelnen Identifizierer und

b) ist der Typ durch Typ-Inferenz klar,

können die runden Klammern wegfallen.

Lange Schreibweise Typen inferred Vollständig abgekürzt
(String s) -> s.length() (s) -> s.length() s -> s.length()
(int i) -> Math.abs( i ) (i) -> Math.abs( i ) i -> Math.abs( i )

Unterschiedlicher Grad von Abkürzungen

Kommen alle Abkürzungen zusammen, lässt sich etwa die Hälfte an Code einsparen. Aus (int i) -> { return Math.abs( i ); } wird einfach i -> Math.abs( i ).

Syntax-Hinweis

Nur bei genau einem Lambda-Parameter können die Klammern weggelassen werden, da es sonst Mehrdeutigkeiten gibt, für die es sonst wieder komplexe Regeln zur Auflösung geben müsste. Heißt es etwa foo( k, v -> { … } ) ist es unklar, ob foo zwei Parameter deklariert. Ist das zweite Argument ein Lambda-Ausdruck, oder handelt es sich um nur genau einen Parameter, wobei dann ein Lambda-Ausdruck übergeben wird, der selbst zwei Parameter deklariert. Um Probleme wie diesen aus dem Weg zu gehen, können Entwickler auf den ersten Blick sehen, dass foo( k, v -> { … } ) eindeutig für Parameter steht, und foo( (k, v) -> { … } ) nur einen Parameter besitzt.

Java 8 Syntax-Migration mit Eclipse

Seit der Version Eclipse 4.4 (bzw. Eclipse 4.3 mit Java 8-Erweiterung) integriert die IDE einen Code-Transformator, der Klassen-Implementierung von funktionalen Schnittellen in Lambda-Ausdrücke verkürzen kann. Diese Transformationen ist über die Quick-Fix/Quick-Assist möglich.

Eclipse_ConvertToLambda

Leider kann diese Umsetzung (bisher) nicht für ein ganzes Projekt erfolgen, und keine umfangreiche Code-Basis komplett und automatisch auf die neuen Lambda-Ausdrücke gebracht werden.

Java 8 Syntax-Migration mit NetBeans

Die aktuelle NetBeans IDE bringt einen Code-Transformer mit, der elegant die Code-Basis durchsucht und Transformationen automatisch durchführt. Das Menü Refactor bietet den Unterpunkt Inspect and Transform…, der zu einem Dialog führt:

NB_InspectAndTransform

Unter Manage… öffnet sich ein weiter Dialog, bei dem JDK Migration Support aktiviert ist und einige Änderungen voreingestellt sind, neu für Java 8 ist Covert to Lambda or Method References. Auch die Transformationen in die neue Java 7-Syntax sind voreingestellt, aktiviert werden kann: Can use Diamond, Convert to try-with-resources, Join catch sections using mulitcatch, Use specific catch und Use switch over Strings when possible.

NB_InspectAndTransform2

Wer neu Programmcode mit NetBeans schreibt wird gleich auf die neue Syntax hingewiesen. Vervollständigungen nutzen ganz natürlich zum Beispiel den Diamond-Operator.

Metadata space in JDK 8 HotSpot JVM

Wenn es um Java geht, müssen wir immer unterscheiden, ob es um die Sprache selbst geht, um die Bibliotheken, um die JVM oder um die Implementierung von Oracle, die das JDK darstellt. Eine zentrale Neuerung in der Version 8 der HotSpot JVM ist der Umgang bei der internen Repräsentation von Klassen und Metadaten; die Daten landen nicht mehr auf dem Java-Heap, sondern im nativen Speicher, der Metaspace heißt. Vorher gehörte ein java.lang.OutOfMemoryError: PermGen space zu bei großen Anwendungen zum Alltag[1], das Problem ist (im Prinzip) Vergangenheit, es gibt kein Perm-Space mehr in der JDK 8 JVM; die Oracle JRockit und JVM von IBM hatten übrigens immer Metaspace. Der Metaspace-Speicherbedarf kann mit dem Schalter MaxMetaspaceSize begrenzt werden, ein Übertritt führt zur java.lang.OutOfMemoryError: Metadata space. Eine Begrenzung ist nicht verkehrt, das Ergebnis unter Java 7 und Java 8 aber das gleiche, ein OutOfMemoryError.


[1] http://stackoverflow.com/questions/88235/dealing-with-java-lang-outofmemoryerror-permgen-space-error

Official Eclipse Support for Java 8

So ist zu lesen unter https://dev.eclipse.org/mhonarc/lists/eclipse.org-committers/msg00948.html:

The Eclipse top-level project is very proud to announce official support for Java 8. Starting with I20140318-0830 all Luna (4.4) builds contain the Eclipse support for Java™ 8. For Kepler SR2 (4.3.2) a feature patch is available. For future builds visit our downloads page.
The Java™ 8 support contains the following:
– Eclipse compiler implements all the new Java™ 8 language enhancements.
– Significant features, like Search and Refactoring, have been updated to support Java™ 8.
– Quick Assist and Clean Up to migrate anonymous class creations to lambda expressions and back.
– New formatter options for lambdas.
Note that PDE API Tools has not yet adopted the new language constructs. This will be completed in the final Luna release.
Big thanks to everyone who worked on this effort!

Java 8, es ist geschafft

Heute hat Oracle Java 8 freigegeben, es ist vollbracht. Nach langer Verzögerung können wir uns auf Lambda-Ausdrücke, einer neuen Date-Time-API und einer chicen Stream-API freuen. Auch NetBeans 8 ist mit am Start.

Die Java Insel wird in den beiden Bänden umfassend auf Java 8 eingehen, die Bücher werden in ca. 1,5 Monaten in den Buchhandlungen ausliegen und als eBook verfügbar sein. Mit den Ergänzungen bin ich komplett durch, auch das Kapitel über die Date-Time-API war nicht so übel wie gedacht, wobei mich die Typen und Hierarchien schwindlig machen machen wir auch bei den FX-Properties. Insgesamt habe ich beim Schreiben und Ausprobieren einen tiefen Eindruck in Java 8 bekommen und meine interne tutego Schulungssoftware auf Java 8 gebracht. Das passt soweit und ist rund. Ätzend ist, dass Optional nicht serialisierbar ist und bei GWT eine Sonderbehandlung braucht. Auch die Date-Time-Typen sind noch zu neu, als dass sie bei GWT, XStream, JPA berücksichtigt werden.

Java does not suck, ABER …

Java löst bei eigenen Menschen tiefere Aggressionen aus, die dann nur mit 5 Stunden Katzenvideos zu besänftigen sind. In den Tenor will ich nicht einsteigen, da ich Java an sich geil finde und die meisten Kritiken nicht teile: langsam, nicht wirklich plattformunabhängig, GC kommt zur Unzeit, geprüfte/ungeprüfte Ausnahmen, kein private protected, kein Sprachkonstrukt für Properties, keine überladene Operatoren, nicht dynamisch genug, Swing ist langsam, kein Modulsystem, Sicherheitsprobleme, …

Dafür sind es andere (kleinere) Dinge, die mich nerven:

  • Der Java-Bibliothek merkt man das Alter an. Vieles ist damals einfach in den “Container” java.util gewandert, und was warum gibt es eigentlich System und Runtime? Von Markierungsschnittstellen Serializable und RandomAccess kommen wir auch nicht mehr weg, der Vorteil ist aber, dass ein instanceof-Test billig ist. Collections ist ein Sammelsurium von Methoden rund um Collection-Typen. Warum wurden die Methoden nicht auch bei den Collection/List-Objekten festgemacht? Ein List#sort(…) wurde ja mittlerweile nachgereicht.
  • Wir haben für die gleiche Aufgabe gleich mehrere Bibliotheken in der der Java SE. Es gibt drei Gui-Komponentenbibliotheken (AWT, Swing, JavaFX), zwei Datei-APIs (java.io.File und java.nio mit Files, Path, Paths, …), zwei Datum-Zeit-APIs (Date/Calendar und java.time in Java 8).  Neueinsteiger in Projekten werden bei Datum/Zeit vermutliche eine wilde Mischung aus Date/Calendar, Joda-Time und bald Java 8 Date-Time-API finden.
  • Immer mehrere Logging-Frameworks im Projekt und folglich unterschiedliche Konfigurationen. Als es java.util.logging (JUL) noch nicht gab, war log4j quasi Standard. Dann kam JUL, log4j wolle man vereinigen, also kam Meta-Logging wie Commons Logging, später SL4J. Dann wurde es ruhig um log4j, neue Produkte kamen, wie logback. Jetzt startet Log4j 2 wieder durch …
  • Die Unterscheidung zwischen primitiven Datentypen und Wrapper-Typen wird immer irrer, schaut man auf Java 8 in das java.util.function-Paket: BooleanSupplier, DoubleBinaryOperator, DoubleConsumer, DoubleFunction<R>, DoublePredicate, DoubleSupplier, DoubleToIntFunction, DoubleToLongFunction, DoubleUnaryOperator, IntBinaryOperator, IntConsumer, IntFunction<R>, IntPredicate, IntSupplier, IntToDoubleFunction, IntToLongFunction, IntUnaryOperator, LongBinaryOperator, LongConsumer, LongFunction<R>, LongPredicate, LongSupplier, LongToDoubleFunction, LongToIntFunction, LongUnaryOperator, ObjDoubleConsumer<T>, ObjIntConsumer<T>, ObjLongConsumer<T>, ToDoubleBiFunction<T,U>, ToDoubleFunction<T>, ToIntBiFunction<T,U>, ToIntFunction<T>, ToLongBiFunction<T,U> , ToLongFunction<T> (OMG!) Ähnliches in javafx.beans.property auch noch mal.
  • Oracle-Webseiten verschwinden, Sun-Seiten wurden nicht übertragen.  Für die kommende Version vom Buch muss ich zwangsläufig alle Links prüfen, viele leiten auf übergeordnete Seiten weiter und sind verschwunden. Bei einer Seite wie http://docs.oracle.com/javase/7/docs/technotes/tools/ erwartet man, dass man 7 durch 8 ersetzen kann und dann kommt man irgendwie bei der Doku von Java 8 aus, oder? Ist aber nicht so …
  • Bug-Base-IDs und Links stimmen oft nicht, Bug gibt es angeblich nicht.
  • toString()-Methoden sind i.d.R. Debug-Methoden, warum implementiert ein Array –Objekt kein vernünftiges toString()?
  • GlassFish ist nicht mehr kommerziell supported wobei es die RI ist. Wie soll man Kunden dann für so ein tolles Produkt begeistern können?
  • Kein standardisiertes Web-Framework bis aus JSF, das reicht nicht. In der Microsoft-Welt hat man erkannt, dass man auch ein (anderes) MVC-Framework braucht, dort gib es ASP.NET MVC Framework und das klassische ASP.
  • Zwei Compiler; der Standard-Compiler und der Eclipse-Compiler (EJC) sind auf unterschiedlichem Niveau und gibt es neue Java-Version, bauen die Eclipse-Macher alles nach. Das dauert und die Compiler sind nicht identisch. Compiler werden immer komplexer.
  • Unklare Zukunft von NetBeans. Bei NB gibt es nach der kommenden Version NB 8.0 (erwartet April 2014) überhaupt keine Zukunftsaussagen. Viele Entwickler sind aus NB abgezogen worden, ob es überhaupt weiter geht und in welchem Tempo ist offen.
  • Unklare Aussage über die Zukunft von Bibliotheken. Wie wird sich GWT weiter entwickeln? Was ist mit anderen Projekten? Oftmals wird das Intervall von Code-Änderungen immer größer, die Projektseite erfährt keine Updates und das Projekt schläft ein. Wie viele UML-Tools sind schon gekommen und gegangen?
  • Oracle hat in Java 7-Updates 51 die Sicherheitsrichtlinien für belegbare Ports verschärft, mit der Konsequenz dass plötzlich Derby nicht mehr läuft und somit alle Standard-Tutorials für JDBC/JPA schon am Anfang eine Hürde nehmen müssen: “The default socket permissions assigned to all code including untrusted code have been changed in this release. Previously, all code was able to bind any socket type to any port number greater than or equal to 1024. It is still possible to bind sockets to the ephemeral port range on each system. The exact range of ephemeral ports varies from one operating system to another, but it is typically in the high range (such as from 49152 to 65535). The new restriction is that binding sockets outside of the ephemeral range now requires an explicit permission in the system security policy..”
  • Suche in Javadoc ist immer noch nicht möglich, ein wenig JavaScript könnte nicht schaden.

Was nervt euch?

Neue Version vom Apache JSPWiki

Das Apache JSPWiki is ein Wiki-System auf der Basis einfacher Java-Technologien Servlets und JSP. Apache JSPWiki gibt es nun in der Version 2.10, Details unter http://jspwiki.apache.org/.

Some of its features include:

  • WikiMarkup/Structured Text
  • File attachments
  • Templates support
  • Data storage through your choice of two WikiPage Providers, with the capability to create and plug in new ones
  • Security: fine grained control over authorization and authentication, yet simple to configure
  • Easy plugin interface
  • UTF-8 support
  • JSP-based
  • Easy-ish installation
  • Page locking to prevent editing conflicts
  • Support for Multiple Wikis