Was sind Lambda Ausdrücke (lambda expressions)?v.8.0

Lambda Ausdrücke sind Methodenaufrufe von sog. funktionalen Interfaces. Sie führen das Paradigma der funktionalen Programmierung in Java ein und können u.a. helfen, die oft schlecht lesbaren, langen Deklarationen innerer, anonymer Klassen zu vermeiden.

Nur selten oder gar nicht wiederverwendete Klassen werden in Java häufig und gerne als anonyme Klassen geschrieben. Das gängigste Beispiel dürfte die Verwendung eines einfachen ActionListeners sein, der z.B. die Funktionalität eines Buttons steuert:

JButton butt = new JButton("Klick");
butt.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button angeklickt");
    }
});

Die hier deklarierte anonyme Klasse implementiert das Interface ActionListener, das nur eine einzige Methode besitzt. Seit Java 8.0 werden solche Interfaces mit genau einer abstrakten Methode als funktionale Interfaces bezeichnet. Sie müssen zwar nicht, sollten jedoch mit der Annotation @FunctionalInterface versehen werden. Um die Methoden solcher Interfaces zu nutzen, können ab Java 8.0 sog. Lambda Ausdrücke eingesetzt werden. Das obige Beispiel kann hiermit auf die folgende Weise umformuliert werden:

butt.addActionListener(e -> System.out.println("Button angeklickt"));

Es fällt auf, dass hier als Argument der Methode addActionListener() keine vollständige Objektbildung mit new mehr erfolgt. Stattdessen werden der (beliebige) Bezeichner des Methodenarguments und die Anweisungen innerhalb der Parameterklammern von actionPerformed() getrennt von einem Pfeiloperator ('->') notiert. Es wird also eine Kurzform der vollständigen Methodenimplementierung als Parameter der aufgerufenen Methode actionPerformed() angegeben.

Dies kann auch mit mehreren Parametern (einer Methode) erfolgen, nicht jedoch bei anonymen Klassen oder Interfaces, die mehrere Methoden enthalten, da die identifizierende Methodensignatur ja nicht notiert wird. Besitzt eine Methode keinen Parameter, so werden lediglich die leeren runden Klammern notiert:

public class LambdaHello {
    @FunctionalInterface
    interface Printer { void print(); }

    public static void main(String[] args) {
        Printer p = () -> System.out.println("Hallo Welt!");
        p.print();    // "Hallo Welt!"
    }
}

Man erkennt hier, dass Objektbildung und Speicherung in einer Variablen, sowie Methodenimplementierung im einfachsten Fall gerade einmal eine Zeile benötigen.
Das folgende Beispiel zeigt, wie dies mit der Definition eines eigenen Interfaces, zwei Methodenparametern und zwei Anweisungen formuliert wird:

public class LambdaPrintTest {
    @FunctionalInterface
    interface Printer {
        public void print(String s, String t);
    }
    
    public static void main(String[] args) {
        Printer p = (s, t) -> {
            t = t.toUpperCase();
            System.out.println(s + " " + t);
        };
        p.print("Hallo", "Welt");    // Hallo WELT
    }
}

Sollen mehrere Anweisungen in der Methode ausgeführt werden, so werden sie in geschweifte Klammern eingeschlossen und jeweils durch Semikolon abgeschlossen. Bei nur einer Anweisung kann der Einschluss in Klammern entfallen.
Besitzt die Methode einen Rückgabewert, so wird dieser als letztes notiert, ebenfalls durch Semikolon abgeschlossen:

public class LambdaCalculateTest {
    @FunctionalInterface
    interface Rectangle {
        public double getArea(double width, double height);
    }

    public static void main(String[] args) {
        Rectangle rect = (w, h) -> { w*=2; h*=3; return w * h; };
        System.out.println("Flaeche: " + rect.getArea(4, 3));
    }
}
                

Neben der vereinfachten Schreibweise einer anonymen Klasse erlauben Lambda-Ausdrücke auch andere Zugriffsberechtigungen auf Variablen. So kann innerhalb eines Lambda-Ausdrucks direkt auf in der gleichen Codeebene deklarierte Variablen zugegriffen werden, sofern diese final deklariert oder zumindest effektiv final sind. Im folgenden Fall kann deshalb auf die explizite final-Deklaration von multi verzichtet werden.

public class LambdaCalculateTest {
    @FunctionalInterface
    interface Rectangle {
        public double getArea(double width, double height);
    }

    public static void main(String[] args) {
        double multi = 3;
        Rectangle rect = (w, h) -> { w*=multi; h*=multi; return w * h; };
        System.out.println("Flaeche:" + rect.getArea(4, 3));
    }
}

Allerdings sollte in solchen Fällen vorsichtshalber nicht auf eine final-Deklaration verzichtet werden, denn ein Code wie der folgende ist nicht möglich:

//...
double multi = 3;
multi = 8;
Rectangle rect = (w, h) -> { w*=multi; h*=multi; return w * h; }; // Fehler
//...

Vertiefung

Java ist eine objektorientierte Programmiersprache. Der Zustand von Objekten bestimmt den Status eines Programms, wobei Funktionen die Eigenschaften der Objekte modifizieren und den Programmablauf steuern.

Lambda-Ausdrücke führen, zusammen mit funktionalen Interfaces, das Paradigma der funktionalen Programmierung in Java ein. Hierbei dienen Funktionen nicht mehr dazu, den Status von vorhandenen Objekten zu modifizieren, vielmehr wird der Programmablauf weitgehend von Objekten getrennt.
Greifen wir auf obiges Beispiel zurück:

@FunctionalInterface
interface Rectangle {
    public double getArea(double width, double height);
}

Da Rectangle ein Interface ist, kann kein Objekt dieses Typs mit new erzeugt werden. Die Verwendung der Methode getArea() ist in der OOP jedoch nur möglich, nachdem eine Klasse, die Rectangle implementiert, erzeugt und innerhalb dieser die Methode konkretisiert (mit einem Methodenkörper versehen) wird. Nach anschließendem Erzeugen des Objektes muss auf diesem die Methode aufgerufen werden. Sie liefert dann die Fläche als Eigenschaft des Objektes.
Bei der Verwendung eines Lambda-Ausdurcks wird dieses Prinzip verlassen.

Rectangle rect = (w, h) -> { w*=2; h*=3; return w * h; };

Anstatt eines Konstruktoraufrufs befindet sich hier auf der rechten Seite vom Zuweisungsoperator eine Methode, die unabhängig von einem implementierenden Objekt verwendet wird. Sie liefert ein eigenständiges Rectangle-Objekt, das zum Zeitpunkt seiner Verwendung gebildet und anschließend wieder verworfen werden kann.

Quellen

http://www.tutego.de/blog/javainsel/2014/03/java-8-umgebung-lambda-ausdruecke-variablenzugriffe/
https://stackabuse.com/guide-to-functional-interfaces-and-lambda-expressions-in-java/

Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.