CodeGym /Kurse /JAVA 25 SELF /Einführung in Lambda-Ausdrücke

Einführung in Lambda-Ausdrücke

JAVA 25 SELF
Level 21, Lektion 0
Verfügbar

1. Einführung

Kurz gesagt: Lambda-Ausdruck — das ist eine Möglichkeit, schnell eine Implementierung eines funktionalen Interfaces „on the fly“ zu erstellen, ohne eine separate Klasse oder eine anonyme Klasse zu deklarieren. Es ist wie eine kleine, namenlose Methode, die man als Argument übergeben oder in einer Variable speichern kann.

Java war lange Zeit eine „klassische“ OOP-Sprache. Wollte man ein Stück Verhalten übergeben, zum Beispiel was bei einem Button-Klick passieren soll, musste man anonyme Klassen schreiben:

button.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick() {
        System.out.println("Button gedrückt!");
    }
});

Mit Java 8 kam die Lambda-Magie:

button.setOnClickListener(() -> System.out.println("Button gedrückt!"));

Dabei () -> System.out.println("Button gedrückt!") ist ein Lambda-Ausdruck.

Formal: Ein Lambda-Ausdruck ist eine kompakte Schreibweise für die Implementierung der einzigen abstrakten Methode eines funktionalen Interfaces.

Warum ist das wichtig?

  • Verhalten als Wert übergeben (z. B. was mit jedem Element einer Liste zu tun ist).
  • Kompakten und gut lesbaren Code schreiben.
  • Moderne Java-APIs nutzen: die Stream-API, Ereignisverarbeitung, asynchrone Aufgaben und vieles mehr.

2. Syntax von Lambda-Ausdrücken

Allgemeine Form

(Parameter) -> Ausdruck
// oder
(Parameter) -> { Codeblock }

Beispiele für verschiedene Interfaces

1. Ohne Parameter (z. B. Runnable):

Runnable r = () -> System.out.println("Hallo, Lambda!");
r.run();

2. Ein Parameter (z. B. Consumer):

Consumer<String> printer = s -> System.out.println("Übergeben: " + s);
printer.accept("Java");

Wenn es nur einen Parameter gibt und sein Typ hergeleitet werden kann, können die Klammern weggelassen werden.
Der Typ String ist der Typ der Variablen (s), die wir übergeben.

3. Mehrere Parameter (z. B. Comparator):

Comparator<Integer> cmp = (a, b) -> a - b;
System.out.println(cmp.compare(10, 5)); // 5

Der Typ Integer ist der Typ der Variablen (a, b), die wir übergeben.

4. Mehrzeiliger Körper (geschweifte Klammern erforderlich und return, wenn es eine explizite Rückgabe gibt):

Function<Integer, Integer> square = x -> {
    int result = x * x;
    return result;
};
System.out.println(square.apply(6)); // 36

Der erste Integer ist der Rückgabetyp der Funktion, der zweite Integer ist der Parametertyp.

Kurzformen und Prägnanz

  • Wenn der Körper nur aus einem Ausdruck besteht, können Klammern und return entfallen.
  • Wenn es keine Parameter gibt — schreibt man leere Klammern: () -> ...
  • Bei genau einem Parameter — sind Klammern optional: x -> ...
  • Bei mehr als einem Parameter — sind Klammern erforderlich: (a, b) -> ...

Übersichtstabelle

Schauen wir, wie dieselbe Idee mit einer Lambda und mit einer anonymen Klasse geschrieben wird:

Interface Lambda-Ausdruck (Beispiel) Äquivalent einer anonymen Klasse
Runnable
() -> System.out.println("Hi")
new Runnable() { public void run() { System.out.println("Hi"); } }
Consumer<String>
s -> System.out.println(s)
new Consumer<String>() { public void accept(String s) { System.out.println(s); } }
Comparator<Integer>
(a, b) -> a - b
new Comparator<Integer>() { public int compare(Integer a, Integer b) { return a - b; } }

Beispiel mit eigenem Interface

Angenommen, wir haben ein funktionales Interface:

@FunctionalInterface
interface Operation {
    int apply(int a, int b);
}

Früher implementierte man es so:

Operation sum = new Operation() {
    @Override
    public void apply(int a, int b) {
        return a + b;
    }
};

Jetzt – modern:

Operation sum = (a, b) -> a + b;
System.out.println(sum.apply(3, 5)); // 8

Beispiele für Standard-Interfaces

Runnable:

Runnable hello = () -> System.out.println("Hello from thread!");
new Thread(hello).start();

Comparator:

List<String> list = Arrays.asList("Apfel", "Banane", "Kiwi");
list.sort((a, b) -> a.length() - b.length());
System.out.println(list);

Function:

Function<String, Integer> parse = s -> Integer.parseInt(s);
System.out.println(parse.apply("123")); // 123

3. Sichtbarkeit und Variablen-Capture

Variablen aus dem äußeren Kontext (effectively final)

Lambda-Ausdrücke können Variablen aus der umgebenden Methode verwenden. Es gilt jedoch eine Regel: Solche Variablen müssen effectively final sein — also entweder explizit als final deklariert oder einfach nach der Initialisierung unverändert bleiben.

Beispiel:

String prefix = "Ergebnis: ";
Function<Integer, String> f = x -> prefix + (x * 2);
// prefix ist hier "eingefroren" — danach darf er nicht mehr geändert werden
System.out.println(f.apply(5)); // Ergebnis: 10

Wenn versucht wird, prefix zu ändern, nachdem es in der Lambda verwendet wurde, meldet der Compiler einen Fehler.

Warum ist das so?
Ein Lambda-Ausdruck kann nach dem Verlassen der Methode aufgerufen werden, in der die Variable deklariert wurde. Um „magische“ Bugs zu vermeiden, erlaubt Java die Verwendung nur unveränderlicher Variablen.

Unterschied zu anonymen Klassen

In anonymen Klassen gilt dieselbe Regel: Variablen aus der äußeren Methode müssen final/effectively final sein. Es gibt aber Nuancen bei der Sichtbarkeit: In einer anonymen Klasse bezieht sich this auf die anonyme Klasse selbst, in einer Lambda hingegen auf die äußere Klasse.

public class Demo {
    public void test() {
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println(this); // Ausgabe: Demo$1 (anonyme Klasse)
            }
        };

        Runnable r2 = () -> System.out.println(this); // Ausgabe: Demo (äußere Klasse)

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

Lambda und Klassenfelder

Ein Lambda-Ausdruck kann ohne Einschränkungen auf Felder der äußeren Klasse zugreifen:

public class Counter {
    private int base = 10;

    public void printSum(int x) {
        Function<Integer, Integer> sum = y -> base + y + x;
        System.out.println(sum.apply(5));
    }
}

Hier gilt: base — darf verändert werden (das ist ein Klassenfeld).
x — muss effectively final sein.

4. Praxis: wir schreiben einige Lambda-Ausdrücke

Beispiel: Filtern einer Zahlenliste

List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6);

nums.stream()
    .filter(n -> n % 2 == 0)
    .forEach(n -> System.out.println("Gerade: " + n));

Mehr über die Stream-API erfahren Sie auf Level 30 :P

Beispiel: String-Transformationsfunktion

Function<String, String> capitalize = s -> s.toUpperCase();
System.out.println(capitalize.apply("java")); // JAVA

Beispiel: eigenes funktionales Interface

@FunctionalInterface
interface StringTransformer {
    String transform(String s);
}

StringTransformer exclaim = s -> s + "!";
System.out.println(exclaim.transform("Hallo")); // Hallo!

Beispiel: Verwendung von Variablen aus dem äußeren Kontext

int factor = 2;
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(n -> System.out.println(n * factor));
// factor darf danach nicht mehr geändert werden!

5. Typische Fehler beim Arbeiten mit Lambda-Ausdrücken

Fehler Nr. 1: Versuch, eine von der Lambda erfasste Variable zu ändern.
Solcher Code lässt sich nicht kompilieren — die Variable muss effectively final sein:

int sum = 0;
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(n -> sum += n); // Fehler: sum ist nicht final!

Wenn Sie Werte akkumulieren müssen — verwenden Sie ein Array oder ein Wrapper-Objekt.

Fehler Nr. 2: Verwechslung des Gültigkeitsbereichs von this.
Innerhalb eines Lambda-Ausdrucks verweist this auf die äußere Klasse, nicht auf die Lambda (im Gegensatz zur anonymen Klasse).

Fehler Nr. 3: Geschweifte Klammern und return im mehrzeiligen Lambda-Ausdruck vergessen.
Wenn der Körper der Lambda nicht nur aus einem Ausdruck besteht, sind Klammern und return erforderlich:

Function<Integer, Integer> square = x -> {
    int y = x * x;
    return y;
};

Fehler Nr. 4: Falsche Typzuordnung eines Lambda-Ausdrucks.
Ein Lambda-Ausdruck implementiert immer ein funktionales Interface. Man kann nicht einfach schreiben:

var f = x -> x + 1; // Fehler! Unklar, welcher Interfacetyp.

Der Typ muss explizit angegeben werden:

Function<Integer, Integer> f = x -> x + 1;
1
Aufgabe
JAVA 25 SELF, Level 21, Lektion 0
Gesperrt
Systemdurchsage 📣
Systemdurchsage 📣
1
Aufgabe
JAVA 25 SELF, Level 21, Lektion 0
Gesperrt
Standardisierung der Benutzereingabe 📝
Standardisierung der Benutzereingabe 📝
1
Aufgabe
JAVA 25 SELF, Level 21, Lektion 0
Gesperrt
Rangliste der Sportler nach "Schlagkraft" 🥊
Rangliste der Sportler nach "Schlagkraft" 🥊
1
Aufgabe
JAVA 25 SELF, Level 21, Lektion 0
Gesperrt
Dynamische Berichterstellung 📊
Dynamische Berichterstellung 📊
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION