CodeGym /Kurse /JAVA 25 SELF /Verwendung von Lambdas in Collections und Streams

Verwendung von Lambdas in Collections und Streams

JAVA 25 SELF
Level 48 , Lektion 1
Verfügbar

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
forEach(Consumer<T>)
Für jedes Element
x -> ...
list.forEach(s -> ...)
removeIf(Predicate<T>)
Löscht Elemente nach Bedingung
x -> ...
list.removeIf(s -> ...)
sort(Comparator<T>)
Sortiert Elemente
(a, b) -> ...
list.sort((a, b) -> ...)
replaceAll(UnaryOperator<T>)
Ersetzt jedes Element
x -> ...
list.replaceAll(s -> ...)
filter(Predicate<T>)
Filtert den Stream
x -> ...
stream.filter(s -> ...)
map(Function<T, R>)
Transformiert Elemente
x -> ...
stream.map(s -> ...)
forEach(Consumer<T>)
Iteration über den Stream
x -> ...
stream.forEach(s -> ...)
sorted(Comparator<T>)
Sortierung im Stream
(a, b) -> ...
stream.sorted((a, b) -> ...)

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.

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION