1. Lambda-Ausdrücke und Collections
Erinnern wir uns daran, wie die Verarbeitung von Collections vor den Lambda-Ausdrücken aussah. Angenommen, wir haben eine Liste von Strings und wollen sie auf dem Bildschirm ausgeben:
List<String> list = Arrays.asList("Katze", "Hund", "Igel");
for (String s : list) {
System.out.println(s);
}
Alles einfach, aber wenn wir zum Beispiel alle leeren Strings aus der Liste entfernen wollen, müssen wir eine Schleife mit Bedingung schreiben und manchmal sogar einen Iterator verwenden (sonst gibt es eine ConcurrentModificationException). Oder, sagen wir, die Sortierung nach der Länge eines Strings:
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
Schon bei so einer einfachen Aufgabe – bereits 5 Zeilen Code und jede Menge „lauter“ Klammern. Wie optimieren? Die Antwort kennen Sie bereits: Lambda-Ausdrücke.
Einsatz von Lambda-Ausdrücken in Collection-Methoden
Seit Java 8 haben die Collection-Interfaces neue Methoden erhalten, die funktionale Interfaces akzeptieren – das heißt, wir können ihnen Lambda-Ausdrücke übergeben. Hier sind die beliebtesten:
- forEach(Consumer<T> action)
- removeIf(Predicate<T> filter)
- sort(Comparator<T> c)
- replaceAll(UnaryOperator<T> operator)
Beispiel: forEach
Alle Elemente der Liste ausgeben (alter Ansatz):
for (String s : list) {
System.out.println(s);
}
Jetzt – mit Lambda:
list.forEach(s -> System.out.println(s));
Oder noch kürzer, wenn man den „Java-Guru“ spielen möchte:
list.forEach(System.out::println); // Methodenreferenz, besprechen wir später
Beispiel: removeIf
Alle leeren Strings aus der Liste entfernen:
List<String> animals = new ArrayList<>(Arrays.asList("Katze", "", "Hund", "Igel", ""));
animals.removeIf(s -> s.isEmpty());
System.out.println(animals); // [Katze, Hund, Igel]
Beispiel: sort
Liste nach String-Länge sortieren:
List<String> animals = Arrays.asList("Katze", "Hund", "Igel", "Elefant");
animals.sort((a, b) -> a.length() - b.length());
System.out.println(animals); // [Hund, Igel, Katze, Elefant]
Beispiel: replaceAll
Alle Strings in Großbuchstaben umwandeln:
List<String> animals = new ArrayList<>(Arrays.asList("Katze", "Hund", "Igel"));
animals.replaceAll(s -> s.toUpperCase());
System.out.println(animals); // [KATZE, HUND, IGEL]
2. Stream API und Lambda-Ausdrücke
Mit Java 8 kam das Stream API – ein mächtiges Werkzeug zur Verarbeitung von Collections im funktionalen Stil. Streams erlauben es, mit Methodenketten zu filtern, zu transformieren, zu sortieren und Collections zu sammeln. Und all diese Methoden akzeptieren Lambda-Ausdrücke!
Wichtig: Eine vollständige Behandlung des Stream API folgt später, jetzt – nur grundlegende Beispiele, um die Rolle der Lambdas zu verstehen.
Beispiel: Filtern
Nur Strings mit mehr als 3 Zeichen behalten:
List<String> animals = Arrays.asList("Katze", "Elefant", "Igel", "Krokodil");
animals.stream()
.filter(s -> s.length() > 3)
.forEach(System.out::println); // Katze, Elefant, Igel, Krokodil
Beispiel: Transformation (map)
Alle Strings in Großbuchstaben umwandeln:
List<String> animals = Arrays.asList("Katze", "Elefant", "Igel");
List<String> upper = animals.stream()
.map(s -> s.toUpperCase())
.collect(Collectors.toList());
System.out.println(upper); // [KATZE, ELEFANT, IGEL]
Beispiel: Sortierung
Eine nach Länge sortierte Liste erhalten (ohne die Ausgangsliste zu verändern):
List<String> animals = Arrays.asList("Katze", "Elefant", "Igel", "Krokodil");
List<String> sorted = animals.stream()
.sorted((a, b) -> a.length() - b.length())
.collect(Collectors.toList());
System.out.println(sorted); // [Igel, Katze, Elefant, Krokodil]
3. Vergleich mit anonymen Klassen
Vergleichen wir, wie derselbe Code mit einer anonymen Klasse und mit einer Lambda aussieht.
Sortierung nach String-Länge
Anonyme Klasse:
animals.sort(new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
Lambda-Ausdruck:
animals.sort((a, b) -> a.length() - b.length());
Fazit:
Ein Lambda-Ausdruck spart viele Zeilen und macht den Code lesbarer. Weniger Klammern, weniger Lärm – mehr Essenz!
Entfernen leerer Strings
Anonyme Klasse:
animals.removeIf(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.isEmpty();
}
});
Lambda-Ausdruck:
animals.removeIf(s -> s.isEmpty());
4. Praxis: kurze Aufgaben mit Lambda-Ausdrücken
Probieren wir in der Praxis aus, Lambda-Ausdrücke in einem Mini-Programm zur Arbeit mit einer Benutzerliste einzusetzen.
Beispiel 1: Benutzer nach Alter filtern
class User {
String name;
int age;
User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
List<User> users = Arrays.asList(
new User("Alice", 17),
new User("Bob", 25),
new User("Charlie", 15)
);
users.stream()
.filter(u -> u.age >= 18)
.forEach(System.out::println); // Bob (25)
Beispiel 2: Benutzer nach Namen sortieren
List<User> users = Arrays.asList(
new User("Alice", 17),
new User("Bob", 25),
new User("Charlie", 15)
);
users.sort((u1, u2) -> u1.name.compareTo(u2.name));
System.out.println(users);
// [Alice (17), Bob (25), Charlie (15)]
Beispiel 3: Benutzerliste in Namensliste transformieren
List<String> names = users.stream()
.map(u -> u.name)
.collect(Collectors.toList());
System.out.println(names); // [Alice, Bob, Charlie]
Beispiel 4: Alle Minderjährigen entfernen
List<User> users = new ArrayList<>(Arrays.asList(
new User("Alice", 17),
new User("Bob", 25),
new User("Charlie", 15)
));
users.removeIf(u -> u.age < 18);
System.out.println(users); // [Bob (25)]
5. Nützliche Feinheiten
Besonderheiten: Sichtbarkeit und „effektiv final“-Variablen
Ein Lambda-Ausdruck kann Variablen aus der äußeren Methode verwenden, aber nur, wenn sie final oder „effektiv final“ sind (d. h. nach der Initialisierung nicht mehr verändert werden).
int minAge = 18;
users.stream()
.filter(u -> u.age >= minAge)
.forEach(System.out::println);
Wenn Sie versuchen, minAge nach der Verwendung in der Lambda zu ändern, meldet der Compiler einen Fehler.
Tabelle: zentrale Methoden von Collections und Streams mit Lambda-Ausdrücken
| Methode von Collection/Stream | Was sie tut | Typ des Lambda-Ausdrucks | Beispiel |
|---|---|---|---|
|
Für jedes Element | |
|
|
Löscht Elemente nach Bedingung | |
|
|
Sortiert Elemente | |
|
|
Ersetzt jedes Element | |
|
|
Filtert den Stream | |
|
|
Transformiert Elemente | |
|
|
Iteration über den Stream | |
|
|
Sortierung im Stream | |
|
7. Typische Fehler
Fehler Nr. 1: Lambda ist zu lang. Wenn Ihr Lambda-Ausdruck bereits 5 Zeilen Code, Bedingungen, Schleifen und try-catch enthält – dann sollten Sie diesen Code wahrscheinlich in eine eigene Methode auslagern. Lambdas eignen sich für kurze Logik.
Fehler Nr. 2: Verwendung veränderlicher Variablen. Wenn Sie versuchen, innerhalb eines Lambdas eine Variable aus der äußeren Methode zu ändern (z. B. einen Zähler), lässt der Compiler das nicht zu. Die Variable muss final oder effektiv final sein.
Fehler Nr. 3: Vergessen, dass Methoden von Collections/Streams nicht immer die Ausgangs-Collection ändern. Zum Beispiel verändert stream().filter(...) die Ausgangsliste nicht, sondern gibt einen neuen Stream zurück. Wenn Sie eine Collection erhalten möchten – verwenden Sie collect(Collectors.toList()).
Fehler Nr. 4: Lambda-Ausdruck passt nicht zum Typ. Wenn eine Methode z. B. Comparator<T> erwartet und Sie versuchen, ein Lambda mit einem Parameter (statt zwei) zu übergeben – führt das zu einem Kompilierungsfehler.
Fehler Nr. 5: Lesbarkeit geht bei verschachtelten Lambdas verloren. Wenn Sie eine Kette aus map, filter, forEach haben und in jedem Lambda noch ein weiteres Lambda steckt – wird der Code unlesbar. In solchen Fällen ist es besser, Ausdrücke in einzelne Schritte zu zerlegen oder Teile in Methoden auszulagern.
GO TO FULL VERSION