Lambda Ausdrücke in Java 8 implementieren

Lambda Ausdrücke in Java 8

Die wichtigste Neuerung in Java 8 sind die sog. Lambda-Ausdrücke. Die Verwendung von Lambda-Ausdrücken führt in der Regel zu einem radikal verkürzten und konzisen Programmcode.

Lambda-Ausdrücke können überall dort verwendet werden, wo Interfaces implementiert werden, welche genau eine einzige abstrakte Methode spezifizieren. Solche Interfaces werden als funktionale Interfaces bezeichnet. Java 8 hat eine Vielzahl solcher Interfaces neu eingeführt: Supplier, Function, Operator, Predicate, Consumer etc., welche u.a. in den erweiterten Features der Standardbibliothek extensiv genutzt werden.

Im folgenden werden wir das Konzept der Lambda-Ausdrücke anhand einer einfachen Swing-Anwendung erläutern. Die Anwendung hat zwei JButtons, deren Betätigung eine bestimmte Aktion auslösen soll. Wir benötigen also für jeden JButton einen ActionListener. Wir implementieren diese ActionListener zunächst mittels anonymer Klassen.

Die Anwendung präsentiert sich dem Benutzer wie folgt:

Java 8 Anwendung

Wir definieren eine von JFrame abgeleitete Klasse:

Der JButton-Methode addActionListener wird eine Instanz einer anonymen Klasse übergeben, die das (funktionale!) Interface ActionListener implementiert. Innerhalb der actionPerformed-Methode dieser Klasse kann die Instanz der äußeren Klasse via MyFrame.this angesprochen werden – und somit die onOpen-Methode der äußeren Klasse aufrufen zu können.

Die obige Formulierung enthält „boilerplate code“. Der Compiler weiß, dass an die addActionListener-Methode ein ActionListener übergeben werden muss (würden wir einen FocusListener übergeben, würde er sich beschweren:..). Also sollten wir new ActionListener() { und eine abschließende } weglassen können:

Der Compiler weiß weiterhin, dass das Interface ActionListener genau eine einzige Methode enthält: actionPerformed. Also sollten wir auch hier auf die explizite Nennung des Namens verzichten können:

Was bleibt übrig?

Übrig bleibt nur die Parameterliste von actionPerformed und die Implementierung dieser Methode.

Der Compiler kann aufgrund dieser Kurzform wieder automatisch die ausführliche Form ableiten. Wir müssen ihm nur sagen, dass er eben dies tun soll – indem wir zwischen der Parameterliste und der Implementierung einen Pfeil einfügen:

Diese Formulierung wird nun vom Compiler anstandslos übersetzt.

Können wir die Formulierung noch weiter vereinfachen?

Dem Compiler ist auch der Typ des Parameters bekannt: wir können uns also auf den Namen des Parameters beschränken:

Da nun die Parameterliste nurmehr aus dem Namen genau eines einzigen Parameters besteht, können wir auf die umschließenden Klammern verzichten:

Und da auch die Implementierung nur aus einer einzigen Anweisung besteht, können wir auch die geschweiften Klammern streichen:

Und der Code wird noch schöner, wenn er in einer einzigen Zeile formuliert wird:

Der Ausdruck, der an addActionListener übergeben wird, wird als Lambda-Ausdruck bezeichnet. Wir können einen Lambda-Ausdruck als Kurzform der Instanziierung einer anonymen Klasse begreifen, welche ein funktionales Interface implementiert – wobei von dieser anonymen Klasse dann nurmehr die Parameterliste der Interface-Methode und deren Implementierung übrig bleibt (wobei Parameterliste und Implementierung durch einen Pfeil getrennt werden: die Parameter werden auf die Implementierung „abgebildet“).

Allerdings existieren auch Unterschiede zwischen anonymen Klassen und Lambdas:

In einer anonymen Klasse können z.B. weitere (Hilfs-)Methoden und Attribute definiert werden – in einem Lambda-Ausdruck ist dies natürlich nicht möglich.

In einer anonymen Klasse kann das Objekt dieser anonymen Klasse via this angesprochen werden – und das Objekt der „äußeren“ Klasse via Outer.this (Outer sei der Name der äußeren Klasse). In einem Lambda wird auch via this das Objekt der äußeren(!) Klasse angesprochen: Outer.this und this sind hier identisch (insofern hätten wir in der obigen Lambda-Implementierung auch einfach notieren können: e -> this.onOpen().

Für Lambdas werden im Unterschied zu anonymen Klassen keine eigenen class-Dateien erzeugt. Der Bytecode existiert in der class-Datei der umschließenden Klasse.

Der Typ eine Lambdas kann als solcher nicht berechnet werden – die folgende Zuweisung ist daher illegal(!):

Der Typ eines Lambdas ergibt sich stets nur aus dem Ziel-Typ: dem Typ eines verlangten Parameters oder der Typ einer Variablen. Die folgende Zeile ist legal:

Dieses Feature wird auch als „Target Typing“ bezeichnet.

Resultat: Wir können den obigen Programmcode für die einfache GUI radikal verkürzen:

Ein „Lambda-Objekt“ dient also nurmehr als Träger einer Methode. Nicht das Objekt ist von Belang, sondern nur die in ihm enthaltene Funktionalität. Wir können daher einen Lambda-Ausdruck auch als „Funktion“ betrachten…

Weiterlesen