Lambda-Ausdrücke¶
Lambda-Ausdrücke (Lambdas) sind anonyme Funktionen (Methoden), d.h. Funktionen (Methoden) ohne Namen. Lambdas werden im Zusammenhang von Functional Interfaces verwendet. Im Kontext von Lambdas sprechen wir stets eher von Funktionen als von Methoden. Die prinzipielle Syntax von Lambda-Ausdrücken ist
Lambda-Ausdrücke bestehen aus drei Teilen:
- einer Liste von keinem, einem oder merhrern Parametern, durch Komma getrennt und in runden Klammern
()
. Diesen Parametern muss kein Typ zugewiseen werden. Der jeweilige Tp des Parameters wird aus dem Kontext ermittelt. Besteht die Parameterliste aus genau einem Parameter, können die runden Klammern weggelassen werden. - dem Pfeil
->
, der die Parameterliste mit dem Funktionskörper verbindet. - Dem Funktionskörper, der nur dann in geschweiften Klammern eingefasst ist, wenn er aus mehr als einer Anweissung besteht. Wir können also auch so schreiben:
Beachten Sie, dass hinter der einzelnen Anweisung kein Semikolon steht.
Erstes Beispiel¶
Wir betrachten ein erstes einfaches Beispiel zur Verwendung von Lambdas. Angenommen, wir haben folgende einfache Liste
List<String> staedte = List.of("Berlin", "Hamburg", "München", "Köln", "Frankfurt am Main", "Düsseldorf",
"Stuttgart", "Leipzig", "Dortmund", "Bremen", "Essen" ,"Dresden");
und wollen jedes einzelne Element aus dieser Liste ausgeben. Das hätten wir bis jetzt mit einer for
-Schleife erledigt. Für Collections steht uns die forEach()
-Methode aus dem Iterable-Interface zur Verfügung. Die forEach()
-Methode ist wie folgt deklariert:
Das Interessante darin ist das Consumer-Interface. Dieses stellt die Schnittstelle zu unseren Lambda-Ausdrücken dar. Das Consumer-Interface repräsentiert eine Funktion, die einen einzelnen Parameter akzeptiert und keinen Wert zurückliefert. Wir können nun einen Lambda-Ausdruck als Consumer definieren, z.B.:
Da wir nur genau einen Parameter haben, können wir auch die runden Klammern weglassen:
Sollte unsere Funktion aus mehreren Anweisungen bestehen, verwenden wir geschweifte Klammern (und Semikolon):
staedte.forEach(stadt -> {
System.out.printf("%2d : ", (staedte.indexOf(stadt) + 1) );
stadt= stadt.toUpperCase();
System.out.println(stadt);
});
Wir könnten uns den Consumer
auch zunächst explizit definieren und dann wiederverwenden:
Consumer<String> printStadt = stadt -> {
System.out.printf("%2d : ", (staedte.indexOf(stadt) + 1) );
stadt= stadt.toUpperCase();
System.out.println(stadt);
};
staedte.forEach(printStadt);
Functional Interfaces¶
Wir haben nun bereits Consumer kennengelernt. Consumer ist ein Functional Interface, d.h. es besitzt genau eine abstrakte Methode (accept()
). Lambdas lassen sich nur in Verbindung mit Functional Interfaces verwenden, denn nur dann ist eindeutig, welche Methode durch den Lambda-Ausdruck implementiert wird. Wir betrachten folgendes Beispiel zur Klärung:
@FunctionalInterface
public interface Printable
{
void print(String s);
default void print()
{
print("default");
}
}
Printable
ist ein funktionales Interface, da es nur genau eine abstrakte Methode, nämlich print(String)
enthält. Die print()
-Methode ist eine sogenannte default-Methode und bereits implementiert.
Wir könnten uns nun eine Klasse definieren, die Printable
implementiert, z.B.
public class UseInterface implements Printable
{
@Override
public void print(String s)
{
System.out.println(s);
}
}
und wenn wir nun irgendwo eine Methode haben, die ein Printable
erwartet, kann dieser Methode ein Objekt von UseInterface
übergeben werden:
public class Programmklasse
{
public static void printSomething(Printable p, String s)
{
p.print(s);
}
public static void main(String[] args)
{
UseInterface useInterface = new UseInterface();
printSomething(useInterface, "hallo");
}
}
Das ist natürlich alles sehr aufwändig:
- wir benötigen eine Klasse, die
Printable
implementiert (hier:UseInterface
), - wir benötigen ein Objekt dieser Klasse
und das alles nur, um printSomething()
auszuführen. Einfacher wäre es, wir würden direkt in printSomething()
die print()
-Methode des Interfaces Printable
implementieren. Das geht und zwar unter Verwendung von Lambdas:
Methoden-Referenzen¶
Methoden-Referenzen sind eine syntaktische Abkürzung, um Methoden aufzurufen. Methoden-Referenzen sind somit ein spezieller Fall für Lambda-Ausdrücke. Methoden-Referenzen erkennen wir an folgender Syntax
Handelt es sich um eine statische Methode, steht vor dem ::
der Name der Klasse, bei einer Objektmethode steht die Referenzvariable des Objektes davor.
Bei der Verwendung von Methoden-Referenzen können wir in Lambdas sogar ganz auf die Verwendung der Parameter verzichten. Der Code wird dadurch lesbarer: