Von Elyndra Valen, Senior Entwicklerin bei Java Fleet Systems Consulting
Mit Einblicken von Nova Trent (Junior Dev) und Jamal Hassan (Backend Developer)
Schwierigkeit: 🟡 Mittel
Lesezeit: 30 Minuten
Voraussetzungen: Tag 3 (Generics), Java OOP (Interfaces, anonyme Klassen)
Kurs: Java Erweiterte Techniken – Tag 4 von 10
📖 Java Erweiterte Techniken – Alle Tage
📍 Du bist hier: Tag 4
⚡ Das Wichtigste in 30 Sekunden
Dein Problem: Für einfache Operationen schreibst du endlose anonyme Klassen. Sortieren? 10 Zeilen. Button-Click? 8 Zeilen. Das ist aufgebläht und schwer zu lesen.
Die Lösung: Lambda-Ausdrücke – kompakte Funktionen in einer Zeile.
Heute lernst du:
- ✅ Von anonymen Klassen zu Lambdas (der Aha-Moment!)
- ✅ Lambda-Syntax:
(parameter) -> ausdruck - ✅ Method References:
String::toLowerCase - ✅ Wo Lambdas funktionieren (Functional Interfaces)
- ✅ Praktische Anwendungen: Sortieren, Filtern, Event-Handler
Für wen ist dieser Artikel?
- 🌱 Anfänger: Du verstehst endlich, was
->bedeutet - 🌿 Erfahrene: Method References und Closure-Verhalten meistern
- 🌳 Profis: Effectively final, Performance, Design-Entscheidungen
Zeit-Investment: 30 Minuten Lesen + 45-90 Minuten Praxis
👋 Elyndra: „Der Tag, an dem Java elegant wurde“
Hi! 👋
Elyndra hier für Tag 4. Heute wird’s schön – wir lernen Lambdas! 🎉
Die alte Welt (vor Java 8):
// Liste sortieren - 10 Zeilen für eine simple Operation!
List<String> namen = Arrays.asList("Zebra", "Apfel", "Mango");
Collections.sort(namen, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
Die neue Welt (seit Java 8):
// Gleiche Operation - EINE Zeile! Collections.sort(namen, (a, b) -> a.compareTo(b)); // Noch kürzer mit Method Reference: Collections.sort(namen, String::compareTo); // Am elegantesten: namen.sort(String::compareTo);
Von 10 Zeilen zu 1 Zeile. Das ist die Kraft von Lambdas!
💡 Bereit für die Transformation?
→ Spring zu „Die Lambda-Syntax“
🖼️ Von Anonymous Class zu Lambda

Abbildung 1: Die Evolution von anonymen Klassen zu Lambdas
🟢 GRUNDLAGEN
Was ist ein Lambda-Ausdruck?
Ein Lambda ist eine kompakte Schreibweise für eine Funktion. Statt eine ganze Klasse zu definieren, schreibst du nur den relevanten Code.
Die Syntax:
(Parameter) -> Ausdruck
// Oder mit Block:
(Parameter) -> {
statement1;
statement2;
return ergebnis;
}
Beispiele:
// Kein Parameter
() -> System.out.println("Hallo!")
// Ein Parameter (Klammern optional)
x -> x * 2
(x) -> x * 2
// Mehrere Parameter
(x, y) -> x + y
// Mit Typen (meist nicht nötig)
(int x, int y) -> x + y
// Mit Block
(x, y) -> {
int summe = x + y;
return summe * 2;
}
Der Aha-Moment: Anonyme Klasse → Lambda
Schauen wir uns die Transformation Schritt für Schritt an:
Schritt 1: Anonyme Klasse (klassisch)
Comparator<String> comp = new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
};
Schritt 2: Das Offensichtliche weglassen
// Der Compiler weiß:
// - Es ist ein Comparator<String>
// - Die Methode heißt compare
// - Die Parameter sind Strings
// Also brauchen wir nur noch:
Comparator<String> comp = (String a, String b) -> {
return a.compareTo(b);
};
Schritt 3: Typen weglassen (Type Inference)
Comparator<String> comp = (a, b) -> {
return a.compareTo(b);
};
Schritt 4: Einzeiler ohne Block und return
Comparator<String> comp = (a, b) -> a.compareTo(b);
Von 7 Zeilen zu 1 Zeile. Der gleiche Code, nur kompakter!
Wo funktionieren Lambdas?
Lambdas funktionieren nur mit Functional Interfaces – Interfaces mit genau einer abstrakten Methode.
Beispiele für Functional Interfaces:
| Interface | Methode | Lambda-Beispiel |
|---|---|---|
Runnable | void run() | () -> System.out.println("Hi") |
Comparator<T> | int compare(T, T) | (a, b) -> a.compareTo(b) |
ActionListener | void actionPerformed(e) | e -> handleClick(e) |
Predicate<T> | boolean test(T) | s -> s.isEmpty() |
Function<T,R> | R apply(T) | s -> s.toUpperCase() |
Consumer<T> | void accept(T) | s -> System.out.println(s) |
💬 Nova fragt: „Woher weiß der Compiler, welche Methode gemeint ist?“
Gute Frage! Bei einem Functional Interface gibt es nur eine abstrakte Methode. Der Lambda-Ausdruck ist die Implementierung dieser Methode. Der Compiler leitet alles andere aus dem Kontext ab.
Praktische Beispiele
1. Listen sortieren:
List<String> namen = new ArrayList<>(List.of("Zebra", "Apfel", "Mango"));
// Nach natürlicher Ordnung
namen.sort((a, b) -> a.compareTo(b));
// Nach Länge
namen.sort((a, b) -> a.length() - b.length());
// Umgekehrt
namen.sort((a, b) -> b.compareTo(a));
2. Listen filtern (mit removeIf):
List<Integer> zahlen = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6)); // Alle geraden Zahlen entfernen zahlen.removeIf(n -> n % 2 == 0); // zahlen = [1, 3, 5]
3. Über Listen iterieren:
List<String> namen = List.of("Max", "Anna", "Tom");
// Klassisch
for (String name : namen) {
System.out.println(name);
}
// Mit Lambda
namen.forEach(name -> System.out.println(name));
// Mit Method Reference
namen.forEach(System.out::println);
4. Threads starten:
// Klassisch
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hallo aus Thread!");
}
});
// Mit Lambda
Thread t = new Thread(() -> System.out.println("Hallo aus Thread!"));
🟡 PROFESSIONALS
Method References
Method References sind noch kompakter als Lambdas – wenn du nur eine existierende Methode aufrufst.

Abbildung 2: Die vier Arten von Method References
Die vier Arten:
| Art | Syntax | Lambda-Äquivalent |
|---|---|---|
| Statische Methode | ClassName::staticMethod | x -> ClassName.staticMethod(x) |
| Instanz-Methode (Objekt) | object::method | x -> object.method(x) |
| Instanz-Methode (Typ) | ClassName::method | x -> x.method() |
| Konstruktor | ClassName::new | x -> new ClassName(x) |
Beispiele:
// 1. Statische Methode Function<String, Integer> parser = Integer::parseInt; // Äquivalent: s -> Integer.parseInt(s) // 2. Instanz-Methode eines Objekts String prefix = "Hallo "; Function<String, String> greeter = prefix::concat; // Äquivalent: s -> prefix.concat(s) // 3. Instanz-Methode eines Typs Function<String, String> upper = String::toUpperCase; // Äquivalent: s -> s.toUpperCase() // 4. Konstruktor Supplier<ArrayList<String>> factory = ArrayList::new; // Äquivalent: () -> new ArrayList<>()
Praktische Anwendung:
List<String> namen = List.of("max", "anna", "tom");
// Lambda
List<String> upper1 = namen.stream()
.map(s -> s.toUpperCase())
.toList();
// Method Reference - eleganter!
List<String> upper2 = namen.stream()
.map(String::toUpperCase)
.toList();
Effectively Final – Die Closure-Regel
Lambdas können auf Variablen aus dem umgebenden Scope zugreifen – aber nur wenn diese effectively final sind.
String prefix = "Hallo "; // Effectively final (wird nie geändert)
Consumer<String> greeter = name -> System.out.println(prefix + name);
greeter.accept("Welt"); // "Hallo Welt"
// Das funktioniert NICHT:
String text = "Start";
text = "Geändert"; // Jetzt nicht mehr effectively final!
Consumer<String> broken = s -> System.out.println(text + s); // COMPILE ERROR!
Warum diese Regel?
Lambdas können asynchron ausgeführt werden (z.B. in anderen Threads). Wenn sich die Variable zwischenzeitlich ändern könnte, wäre das Verhalten unvorhersehbar.
Workaround mit Wrapper:
// Wenn du wirklich Änderungen brauchst:
int[] counter = {0}; // Array als Wrapper
list.forEach(item -> counter[0]++); // Geht, aber nicht schön!
// Besser: AtomicInteger
AtomicInteger counter = new AtomicInteger(0);
list.forEach(item -> counter.incrementAndGet());
Lambda vs. Anonyme Klasse – Die Unterschiede
| Aspekt | Lambda | Anonyme Klasse |
|---|---|---|
this | Bezieht sich auf umgebende Klasse | Bezieht sich auf anonyme Klasse |
| Felder | Keine eigenen Felder | Kann Felder haben |
| Mehrere Methoden | Nein (nur Functional Interface) | Ja |
| Performance | Meist besser (invokedynamic) | Neue Klasse wird erstellt |
| Shadowing | Keine lokale Variable kann überschattet werden | Kann Variablen überschatten |
this Unterschied:
public class Example {
private String name = "Example";
public void demo() {
// Lambda: this bezieht sich auf Example
Runnable lambda = () -> System.out.println(this.name); // "Example"
// Anonyme Klasse: this bezieht sich auf die anonyme Klasse
Runnable anon = new Runnable() {
private String name = "Anonymous";
@Override
public void run() {
System.out.println(this.name); // "Anonymous"
}
};
}
}
Comparator-Factory-Methoden
Seit Java 8 gibt es elegante Factory-Methoden für Comparatoren:
List<Person> personen = getPersonen();
// Nach Nachname sortieren
personen.sort(Comparator.comparing(Person::getNachname));
// Nach Nachname, dann Vorname
personen.sort(Comparator
.comparing(Person::getNachname)
.thenComparing(Person::getVorname));
// Umgekehrte Reihenfolge
personen.sort(Comparator
.comparing(Person::getAlter)
.reversed());
// null-safe (nulls ans Ende)
personen.sort(Comparator
.comparing(Person::getEmail, Comparator.nullsLast(String::compareTo)));
🔵 BONUS
Currying und Partial Application
Mit Lambdas kannst du Funktionen „teilweise anwenden“:
// Eine Funktion, die eine Funktion zurückgibt
Function<Integer, Function<Integer, Integer>> add =
a -> b -> a + b;
Function<Integer, Integer> add5 = add.apply(5);
System.out.println(add5.apply(3)); // 8
System.out.println(add5.apply(10)); // 15
Lazy Evaluation mit Supplier
// Ohne Supplier: Wird IMMER berechnet
public void log(String level, String message) {
if (level.equals("DEBUG")) {
System.out.println(message); // message wird immer erzeugt
}
}
// Mit Supplier: Wird nur berechnet wenn nötig
public void log(String level, Supplier<String> messageSupplier) {
if (level.equals("DEBUG")) {
System.out.println(messageSupplier.get()); // Nur wenn DEBUG
}
}
// Verwendung:
log("DEBUG", () -> "Objekt: " + expensiveOperation());
💬 Real Talk: Wann Lambda, wann nicht?
Java Fleet Büro, Donnerstag 15:00. Code Review.
Tom: „Jamal, ich hab überall Lambdas reingehauen. Ist doch jetzt modern, oder?“
list.forEach(item -> {
if (item.isValid()) {
item.process();
item.save();
logService.log(item);
}
});
Jamal: schaut kritisch „Hmm. Wie viele Zeilen hat dein Lambda?“
Tom: „So… 6?“
Jamal: „Regel: Wenn dein Lambda mehr als 2-3 Zeilen hat, extrahiere es in eine Methode.“
// Besser:
list.forEach(this::processItem);
private void processItem(Item item) {
if (item.isValid()) {
item.process();
item.save();
logService.log(item);
}
}
Nova: „Stimmt! Jetzt kann ich die Methode auch einzeln testen.“
Jamal: „Genau. Lambdas sind für kurze, einfache Operationen. Für komplexe Logik nimm eine benannte Methode. Lesbarkeit > Kürze.“
Tom: „Und wann nehme ich eine anonyme Klasse?“
Elyndra: kommt dazu „Wenn du mehr als eine Methode brauchst, oder eigene Felder. Aber das ist selten. 95% deiner Fälle sind Lambdas oder Method References.“
❓ FAQ
Frage 1: Kann ich einen Lambda in einer Variable speichern?
Ja, aber du brauchst den passenden Functional-Interface-Typ:
// Geht NICHT: var lambda = x -> x * 2; // Welcher Typ? Compiler weiß es nicht! // Geht: Function<Integer, Integer> doubler = x -> x * 2; IntUnaryOperator doubler2 = x -> x * 2;
Frage 2: Sind Lambdas schneller als anonyme Klassen?
Meistens ja. Lambdas nutzen invokedynamic, das die JVM optimieren kann. Anonyme Klassen erzeugen bei jedem Aufruf eine neue Klasse. Aber: Der Unterschied ist in den meisten Anwendungen vernachlässigbar.
Frage 3: Kann ich in einem Lambda eine Exception werfen?
Checked Exceptions sind problematisch:
// Geht NICHT:
list.forEach(item -> {
Files.readString(Path.of(item)); // IOException!
});
// Workaround 1: Wrappen
list.forEach(item -> {
try {
Files.readString(Path.of(item));
} catch (IOException e) {
throw new RuntimeException(e);
}
});
// Workaround 2: Eigenes Functional Interface
@FunctionalInterface
interface ThrowingConsumer<T> {
void accept(T t) throws Exception;
}
Frage 4: Was bedeutet :: genau?
:: ist der Method Reference Operator. Er referenziert eine existierende Methode, ohne sie aufzurufen.
// Das ist KEINE Ausführung, sondern eine Referenz:
Function<String, Integer> ref = Integer::parseInt;
// Erst hier wird sie ausgeführt:
int result = ref.apply("42");
Frage 5: Bernd sagt, Lambdas seien „nur syntaktischer Zucker“?
seufz Bernd liegt halb richtig. Lambdas sind syntaktisch kompakter, aber sie haben echte Vorteile:
- Lesbarerer Code – Die Absicht ist klarer
- Weniger Boilerplate – Fokus auf die Logik
- Lazy Evaluation – Code wird nur bei Bedarf ausgeführt
- Stream-API – Ohne Lambdas kaum nutzbar
- Parallelisierung – Einfacher mit Lambda-Ausdrücken
🔍 Psst… „behind the code“ oder „in my feels“? Manche Blogposts haben versteckte Geschichten…
🎁 Cheat Sheet
🟢 Lambda-Syntax
// Kein Parameter
() -> ausdruck
() -> { statements; }
// Ein Parameter
x -> ausdruck
(x) -> ausdruck
// Mehrere Parameter
(x, y) -> ausdruck
(x, y) -> { return ausdruck; }
// Mit Typen
(String s, int n) -> s.repeat(n)
🟡 Method References
// Statische Methode ClassName::staticMethod // Instanz-Methode (Objekt) object::instanceMethod // Instanz-Methode (Typ) ClassName::instanceMethod // Konstruktor ClassName::new
🔵 Comparator-Factory
Comparator.comparing(Person::getName)
.thenComparing(Person::getAge)
.reversed()
.nullsLast()
🎨 Challenge für dich!
🟢 Level 1 – Einsteiger
- [ ] Sortiere eine Liste von Strings nach Länge
- [ ] Ersetze alle anonymen Runnable-Klassen durch Lambdas
- [ ] Nutze
forEachstatt for-Schleife
Geschätzte Zeit: 15-30 Minuten
🟡 Level 2 – Fortgeschritten
- [ ] Sortiere Personen nach Nachname, dann Vorname (Comparator-Chaining)
- [ ] Ersetze Lambdas durch Method References wo möglich
- [ ] Implementiere einen einfachen Event-Handler mit Lambdas
Geschätzte Zeit: 30-60 Minuten
🔵 Level 3 – Profi
- [ ] Schreibe eine
filter()-Methode mit Predicate-Lambda - [ ] Implementiere Lazy Evaluation mit Supplier
- [ ] Erstelle einen Comparator mit null-Handling
Geschätzte Zeit: 45-90 Minuten
📦 Downloads
| Projekt | Für wen? | Download |
|---|---|---|
| tag04-lambdas-starter.zip | 🟢 Mit TODOs | ⬇️ Download |
| tag04-lambdas-complete.zip | 🟡 Musterlösung | ⬇️ Download |
🔗 Weiterführende Links
🇩🇪 Deutsch
| Ressource | Beschreibung |
|---|---|
| Rheinwerk OpenBook: Lambda | Umfassendes Kapitel |
| JavaBeginners: Lambda | Einsteigerfreundlich |
🇬🇧 Englisch
| Ressource | Beschreibung | Level |
|---|---|---|
| Oracle: Lambda Tutorial | Offizielle Doku | 🟢 |
| Baeldung: Java 8 Lambda | Best Practices | 🟡 |
| DZone: Method References | Deep Dive | 🟡 |
📧 Offizielle Dokumentation
👋 Geschafft! 🎉
Was du heute gelernt hast:
✅ Lambda-Syntax: (parameter) -> ausdruck
✅ Method References: String::toUpperCase
✅ Functional Interfaces als Lambda-Ziel
✅ Comparator-Chaining für elegantes Sortieren
✅ Effectively final für Closure-Variablen
Fragen? Schreib uns:
- elyndra.valen@java-developer.online
- nova.trent@java-developer.online
📖 Weiter geht’s!
← Vorheriger Tag: Tag 3: Generics
→ Nächster Tag: Tag 5: Functional Interfaces
Tags: #Java #Lambda #MethodReference #FunctionalProgramming #Java8 #Tutorial
📚 Das könnte dich auch interessieren
© 2025 Java Fleet Systems Consulting | java-developer.online

