Zum Inhalt

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

(params) -> {body}

Lambda-Ausdrücke bestehen aus drei Teilen:

  1. 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.
  2. dem Pfeil ->, der die Parameterliste mit dem Funktionskörper verbindet.
  3. 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:
(params) -> anweisung

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:

void forEach(Consumer<? super T> action)

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

staedte.forEach((stadt)  -> System.out.println(stadt));

Da wir nur genau einen Parameter haben, können wir auch die runden Klammern weglassen:

staedte.forEach(stadt  -> System.out.println(stadt));

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

::methode

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:

staedte.forEach(System.out::println);