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:

// ...
public class MyFrame extends JFrame {

    private final JButton buttonOpen = new JButton("Open");
    private final JButton buttonSave = new JButton("Save");
    
    public MyFrame() {
        this.setLayout(new FlowLayout());
        this.add(this.buttonOpen);
        this.add(this.buttonSave);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.buttonOpen.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                MyFrame.this.onOpen();
            }
        });
        this.buttonSave.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                MyFrame.this.onSave();
            }
        });
        this.setBounds(100, 100, 300, 80);
        this.setVisible(true);
    }
    
    private void onOpen() {
        System.out.println("open...");
    }

    private void onSave() {
        System.out.println("save...");
    }
}

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:

        this.buttonOpen.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                MyFrame.this.onOpen();
            }
        });

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:

this.buttonOpen.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                MyFrame.this.onOpen();
            }
        });

Was bleibt übrig?

this.buttonOpen.addActionListener( 
            (ActionEvent e) {
                MyFrame.this.onOpen();
            }
        );

Ü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:

        this.buttonOpen.addActionListener( 
            (ActionEvent e) -> {
                MyFrame.this.onOpen();
            }
        );

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:

        this.buttonOpen.addActionListener( 
            (e) ->; {
                MyFrame.this.onOpen();
            }
        );
;

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

        this.buttonOpen.addActionListener( 
            e -> {
                MyFrame.this.onOpen();
            }
        );

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

        this.buttonOpen.addActionListener( 
            e -> 
                MyFrame.this.onOpen()
        );

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

this.buttonOpen.addActionListener(e -> MyFrame.this.onOpen());

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(!):

Object obj = e -> this.onOpen();

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:

ActionListener l = e -> this.onOpen();

Dieses Feature wird auch als “Target Typing” bezeichnet.

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

// ...
public class MyFrame extends JFrame {

    private final JButton buttonOpen = new JButton("Open");
    private final JButton buttonSave = new JButton("Save");
    
    public MyFrame() {
        this.setLayout(new FlowLayout());
        this.add(this.buttonOpen);
        this.add(this.buttonSave);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.buttonOpen.addActionListener(e -> this.onOpen());
        this.buttonClose.addActionListener(e -> this.onClose());
        this.setBounds(100, 100, 300, 80);
        this.setVisible(true);
    }
    
    private void onOpen() {
        System.out.println("open...");
    }

    private void onSave() {
        System.out.println("save...");
    }
}

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

Lambda Ausdrücke

Web Development – Ein Java-Klassiker bleibt aktuell

Web Development bleibt auch in den Zeiten der App-Programmierung ein aktuelles Thema. Ursprünglich wurden Browser-Anwendungen nach dem MVC2-Pattern unter Benutzung einer serverseitigen View konzipiert. Heutzutage überwiegen jedoch Single Page-Applikationen. Diese nutzen asynchrone AJAX-Aufrufe unter konsequenter Verwendung einer RESTful Architektur. Das führte zu einer Vielzahl an neuen Frameworks und Sprachen. Doch trotz des grundsätzlichen Wandels sind beide Herangehensweisen weiterhin im Spiel.

MVC2 versus REST

MVC2-Anwendungen werden mit JavaServer Faces, einem Bestandteil der Java Enterprise Edition implementiert. Aber auch das proprietären Spring MVC-Framework ist immer noch sehr beliebt. Beide Frameworks sind ausgereift und bieten ein einfaches Programmiermodell mit mächtigem Funktionsumfang.

RESTful Server werden mit JAX-RS programmiert. Diese Bibliothek ist zwar deutlich jünger als JSF, aber auch wesentlich schlanker, da REST bereits durch den http-Standard umgesetzt ist.

Synergien zwischen Java und JavaScript

In Konkurrenz zu JavaScript-Frameworks wie jQuery, Angular, Backbone und React, um nur einige der Neuentwicklungen zu nennen, verlor Java seine Führungsposition im Web Development. Mit Hilfe eines Tricks können Browser-Anwendungen dennoch auch weiterhin mit Java programmiert werden: Ein Source-to-Source-Compiler transcompiliert bzw. transpiliert Java-Logik nach JavaScript. JavaServer Faces definiert AJAX-Aufrufe deklarativ als Bestandteil der Seitendefinition − eine direkte Browser-Programmierung ist unnötig.

Frameworks wie Google Web Toolkit (GWT) oder Vaadin definieren die Client-Oberfläche und den RESTful Server mit Java-Programmen. Der Client-Code wird direkt nach JavaScript transpiliert. Eclipse RCP- und JavaFX-Anwendungen können nach moderaten Anpassungen im Browser ausgeführt werden. Mit ECMA2016 und Java 8 ähneln sich die beiden Programmiersprachen immer mehr. Eine standardisierte Übersetzung von Java direkt nach JavaScript rückt damit in greifbare Nähe.

 

Bildnachweis: pixabay.com, CCO License @unsplash

Weiterlesen