CodeGym /Kurse /JAVA 25 SELF /Lambda-Ausdrücke: Syntax, Geltungsbereiche

Lambda-Ausdrücke: Syntax, Geltungsbereiche

JAVA 25 SELF
Level 48, Lektion 0
Verfügbar

1. Einführung

Seien wir ehrlich: Anonyme Klassen für ein oder zwei Zeilen Code zu schreiben – das ist, als würde man einen riesigen Lastwagen mieten, um ein einziges Brötchen von der Bäckerei in den Laden zu bringen.

Wenn Sie beispielsweise eine Liste von Strings nach ihrer Länge sortieren wollten, musste man vor Java 8 so schreiben:

List<String> list = Arrays.asList("Apfel", "Banane", "Kiwi");
Collections.sort(list, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

Die Aufgabe ist eigentlich einfach, aber der Code füllt einen halben Bildschirm. Besonders nervig, wenn es davon viele gibt: Der Code wird „laut“, das Wesentliche geht verloren. Entwickler weinten und litten – und dann erfand man Lambda-Ausdrücke. Wir haben sie schon ein wenig kennengelernt; jetzt wiederholen wir und vertiefen unser Wissen.

Was ist ein Lambda-Ausdruck

Ein Lambda-Ausdruck ist eine kompakte Schreibweise für die Implementierung eines funktionalen Interfaces, also eines Interfaces mit genau einer abstrakten Methode (zum Beispiel Comparator, Runnable, Consumer und viele andere).

Kurz gesagt ermöglicht ein Lambda-Ausdruck, eine Funktion „on the fly“ genau dort zu schreiben, wo sie gebraucht wird, ohne eine eigene Klasse oder Methode zu deklarieren.

Allgemeine Syntax:

(Parameter) -> { Rumpf }

Beispiele:

  • (a, b) -> a + b – eine Funktion zur Addition zweier Zahlen
  • x -> x * x – eine Funktion, die eine Zahl quadriert
  • () -> System.out.println("Hello!") – eine Funktion ohne Parameter

Bezug zu funktionalen Interfaces:
Einen Lambda-Ausdruck kann man stets einer Variablen vom Typ eines funktionalen Interfaces zuweisen oder als Argument an eine Methode übergeben, die ein solches Interface erwartet.

2. Syntax von Lambda-Ausdrücken

Ohne Parameter

Runnable r = () -> System.out.println("Hallo, Welt!");
r.run(); // Gibt aus: Hallo, Welt!

Ein Parameter
Wenn es genau einen Parameter gibt, können die Klammern weggelassen werden:

Consumer<String> print = s -> System.out.println(s);
print.accept("Java ist cool!");

Mehrere Parameter
Klammern sind verpflichtend:

Comparator<String> cmp = (a, b) -> a.length() - b.length();

Rumpf mit einem einzelnen Ausdruck
Besteht der Rumpf aus nur einem Ausdruck, sind geschweifte Klammern und return nicht nötig:

Function<Integer, Integer> square = x -> x * x;
System.out.println(square.apply(5)); // 25

Rumpf als Block
Wenn mehrere Anweisungen nötig sind, verwenden Sie geschweifte Klammern und return (falls es einen Rückgabewert gibt):

Function<Integer, Integer> abs = x -> {
    if (x < 0) {
        return -x;
    }
    return x;
};
System.out.println(abs.apply(-3)); // 3

Parametertypen
Meist müssen Parametertypen nicht angegeben werden – der Compiler leitet sie aus dem Kontext ab. Man kann sie aber auch explizit angeben:

Comparator<String> cmp = (String a, String b) -> a.length() - b.length();

Lambda ohne Rückgabewert
Wenn das Interface void zurückgibt, schreiben Sie einfach die Anweisungen:

list.forEach(s -> System.out.println("Element: " + s));

Tabelle: Varianten der Syntax von Lambda-Ausdrücken

Was tun wir Beispiel Kommentar
Ohne Parameter
() -> System.out.println("Hi")
Zum Beispiel für Runnable
Ein Parameter
x -> x * x
Ohne Klammern möglich
Mehrere Parameter
(a, b) -> a + b
Klammern sind Pflicht
Ein einzelner Ausdruck
x -> x + 1
Ohne return und ohne geschweifte Klammern
Codeblock
x -> { int y = x + 1; return y * 2; }
Mit return, falls es ein Ergebnis gibt

3. Anwendung: wo und wie man Lambda-Ausdrücke verwendet

Lambda-Ausdrücke werden vor allem dort eingesetzt, wo „Verhalten“ – also eine Funktion – als Argument übergeben werden soll. Das war eine echte Revolution für Collections, Streams (Stream API), Events und vieles mehr.

Sortieren einer Liste

Vor Java 8:

list.sort(new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

Mit Lambda-Ausdruck:

list.sort((a, b) -> a.length() - b.length());

Erzeugen eines Threads

Thread t = new Thread(() -> System.out.println("Thread gestartet!"));
t.start();

Bearbeiten von Collections

list.forEach(s -> System.out.println(s.toUpperCase()));

Filtern einer Liste

List<String> longWords = list.stream()
    .filter(s -> s.length() > 5)
    .collect(Collectors.toList());

Beispiel: Wir entwickeln unsere Lernanwendung weiter

Angenommen, wir haben eine Liste von Benutzern:

List<String> users = Arrays.asList("Alice", "Bob", "Charlie");

Alle Benutzer ausgeben, deren Namen länger als 4 Zeichen sind:

users.stream()
    .filter(name -> name.length() > 4)
    .forEach(name -> System.out.println("Benutzer: " + name));

4. Geltungsbereich von Variablen in Lambda-Ausdrücken

Lambda-Ausdrücke können Variablen aus der umgebenden Methode verwenden, aber es gibt Feinheiten!

Variablen und Lambdas

Eine Lambda in Java darf nur solche Variablen „capturen“, die sich nach ihrer Initialisierung nicht mehr ändern. Wenn die Variable mit final deklariert ist, ist es offensichtlich. Aber selbst wenn das Wort final nicht dasteht, prüft der Compiler, ob sich der Wert ändert oder nicht. Wenn nicht, betrachtet er sie als „effektiv final“ und lässt sie in der Lambda zu.

Beispiel:

int minLength = 4; // Der Wert ändert sich nirgends
users.forEach(name -> {
    if (name.length() > minLength) {
        System.out.println(name);
    }
});

Das funktioniert, weil minLength derselbe Wert bleibt.

Wenn Sie jedoch nach der Verwendung in der Lambda versuchen, minLength neu zuzuweisen, erhalten Sie einen Compilerfehler:

int minLength = 4;
users.forEach(name -> {
    if (name.length() > minLength) {
        System.out.println(name);
    }
});
minLength = 10; // Fehler! Die Lambda hat den Wert bereits "eingefroren"

Im Grunde ist die Regel sehr einfach: Eine Variable, die in eine Lambda gelangt, muss unveränderlich sein.

Unterschied zu anonymen Klassen

In anonymen Klassen und Lambda-Ausdrücken gelten für Variablen aus der äußeren Methode dieselben Regeln: nur final/effektiv final.

ABER!
Bei Lambda-Ausdrücken bezieht sich this auf das äußere Objekt (die aktuelle Instanz der Klasse), bei einer anonymen Klasse auf die Instanz der anonymen Klasse. Das ist wichtig, wenn Sie innerhalb der Lambda auf Felder oder Methoden der aktuellen Klasse zugreifen.

Beispiel:

public class Example {
    String name = "Äußere Klasse";

    void demo() {
        Runnable r1 = new Runnable() {
            String name = "Anonyme Klasse";
            @Override
            public void run() {
                System.out.println(this.name); // "Anonyme Klasse"
            }
        };

        Runnable r2 = () -> System.out.println(this.name); // "Äußere Klasse"

        r1.run();
        r2.run();
    }
}

5. Typische Fehler im Umgang mit Lambda-Ausdrücken

Fehler Nr. 1: Verwendung einer nicht final/nicht effektiv finalen Variable. Wenn Sie eine Variable ändern, nachdem Sie sie in einer Lambda verwendet haben, weist Sie der Compiler sofort zurecht. Das dient der Sicherheit: Andernfalls wäre unklar, welchen Wert der Variable man verwenden soll.

Fehler Nr. 2: Verwechslung mit this. In einem Lambda-Ausdruck ist this die äußere Klasse, in einer anonymen Klasse die anonyme Klasse selbst. Wenn Sie versuchen, eine Methode der äußeren Klasse aus einer Lambda aufzurufen, funktioniert das; aus einer anonymen Klasse hingegen nicht (falls Sie auf den Kontext der äußeren Klasse hoffen).

Fehler Nr. 3: Lambda ohne Kontext. Ein Lambda-Ausdruck kann nicht für sich allein stehen – er muss entweder einer Variable eines funktionalen Interfaces zugewiesen oder dorthin übergeben werden, wo dieses Interface erwartet wird. Der Versuch, einfach x -> x + 1 außerhalb eines Kontexts zu schreiben, führt zu einem Fehler.

Fehler Nr. 4: Zu komplexe Lambda. Wenn ein Lambda-Ausdruck länger als 3–5 Zeilen wird, ist er schwer zu lesen. In solchen Fällen sollten Sie die Logik in eine eigene Methode auslagern.

1
Aufgabe
JAVA 25 SELF, Level 48, Lektion 0
Gesperrt
Werkstatt zum Sortieren von Namen 📜
Werkstatt zum Sortieren von Namen 📜
1
Aufgabe
JAVA 25 SELF, Level 48, Lektion 0
Gesperrt
Mystischer Zahlenklassifikator 🔮
Mystischer Zahlenklassifikator 🔮
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION