In Java bezeichnet man ein Interface mit genau einer abstrakten Methode als funktionales Interface (engl. Single Abstract Method, SAM). Solche Interfaces sind die Grundlage für Lambda-Ausdrücke. Ein Lambda-Ausdruck ist eine komprimierte Schreibweise, mit der man die Implementierung dieser einen Methode direkt als Ausdruck übergibt, z. B. als Parameter einer Methode.
Beispiel
Wir nutzen ein kleines Interface Checkable, eine Hilfsklasse Rectangle sowie eine Demo-Klasse LambdaDemo. Die Methode checkValidity() erwartet einen Parameter vom Typ Checkable und ruft dessen einzige Methode check() auf. Die Implementierung dieser Methode wird bei den Aufrufen als Lambda-Ausdruck übergeben.

Das Beispiel-Interface Checkable mit der einzigen Methode check()
Das Interface Checkable ist funktional, weil es genau eine abstrakte Methode check() deklariert, die einen Wahrheitswert (true oder false) liefert.

Die Beispiel-Klasse Rectangle
Die Klasse kapselt die zwei Attribute length und width (int) und stellt die Getter-Methoden getLength() und getWidth() zur Verfügung.

Die Beispiel-Klasse LambdaDemo
Erläuterung
In Zeile 6 wird zunächst ein Objekt rect der Klasse Rectangle erzeugt.
Die Methode checkValidity() (in den Zeilen 17 bis 21) erwartet als Parameter ein Objekt des Interfaces Checkable und liefert einen String zurück.
In Zeile 19 wird die Interface-Methode check() aufgerufen, die true oder false zurückgibt. Die Methode checkValidity() gibt dann entsprechend "valid" oder "invalid" zurück.
Verwendung von Lamda-Ausdrücken
In den Zeilen 8 bis 9 wird checkValidity() mit einem Lambda-Ausdruck aufgerufen:
() -> rect.getLength() > 0 && rect.getWidth() > 0
Wir wollen diesen Lambda-Ausdruck nun näher analysieren. Wenn eine "normale" Klasse das Interface Checkable implementieren würde, dann könnte die Implementation der einzigen Methode check() so aussehen:
public boolean check() { return (rect.getLength() > 0 && rect.getWidth() > 0); }
Um aus dieser Methoden-Implementation einen Lambda-Ausdruck zu machen, gehen wir folgendermaßen vor:
Zunächst entfernen wir den Zugriffsmodifikator public sowie den Rückgabetyp boolean und schreiben das Ganze in einer Zeile:
check() { return (rect.getLength() > 0 && rect.getWidth() > 0);}
Auf den Methodennamen können wir ebenfalls verzichten, der Compiler kann aus dem Zusammenhang entnehmen, welche Methode gemeint ist, da das funktionale Interface ja nur eine einzige Methode besitzt.
() { return (rect.getLength() > 0 && rect.getWidth() > 0);}
Da die Methode nur eine einzige Anweisung enthält, können wir auch auf die geschweiften Klammern verzichten. Und der return-Befehl ist ebenfalls überflüssig, da aus dem Interface klar hervorgeht, dass ein boolean-Wert zurückgegeben wird:
() (rect.getLength() > 0 && rect.getWidth() > 0)
Schließlich können wir auch auf die runden Klammern verzichten, welche die rechte Seite des Lambda-Ausdrucks einschließen. Aber dafür müssen wir jetzt den Pfeil-Operator einsetzen, der die Parameterliste (hier leer) von dem eigentlichen Lambda-Ausdruck trennt:
() -> rect.getLength() > 0 && rect.getWidth() > 0
Damit ist unser Lambda-Ausdruck fertig. Auf der linken Seite des Pfeil-Operators steht eine leere Parameterliste, weil die Methode check() des Interfaces keine Parameter erwartet. Auf der rechten Seite des Pfeil-Operators steht nun ein Ausdruck, der die Methode check() implementiert. Diese spezielle Implementation überprüft, ob die Länge und die Breite des Rechtecks größer als Null sind.
Das Ganze wiederholt sich in den Zeilen 12 und 13. Nur wird hier eine andere Implementation der Methode check() als Parameter an checkValidity() übergeben. Jetzt soll überprüft werden, ob Länge und Breite des Rechtecks kleiner oder gleich 10 sind.
Quellen:
- Q. Charatan and A. Kans: Programming in Two Semesters. Springer Nature Switzerland AB 2022.