jario_
Zur Übersicht

Kapitel 11

Lambdas und Streams

Bisher hast du Methoden Werte übergeben: Zahlen, Texte, Listen. Mit einem Lambda übergibst du ein Stück Verhalten, zum Beispiel „erhöhe um 50“ oder „ist länger als 4 Zeichen“. Darauf bauen Streams auf: Ketten von Schritten wie filtern, umwandeln und zusammenzählen, ganz ohne eigene Schleife.

Lambdas und Methodenreferenzen

Ein Lambda ist eine Funktion ohne Namen. Links vom Pfeil stehen die Parameter, rechts das Ergebnis:

  • p -> p + 50: ein Parameter
  • (a, b) -> a + b: zwei Parameter, in Klammern
  • w -> { ... return ...; }: mehrere Zeilen in geschweiften Klammern, mit return

Ein Lambda passt überall, wo ein Interface mit genau einer Methode erwartet wird. Die häufigsten aus java.util.function:

  • Predicate<T>: eine Bedingung, ausgeführt mit test(x), liefert boolean
  • Function<T, R>: wandelt ein T in ein R um, ausgeführt mit apply(x)

Listen haben Methoden, die ein Lambda nehmen: replaceAll ersetzt jedes Element, removeIf entfernt alle, für die die Bedingung gilt. Ruft ein Lambda nur eine vorhandene Methode auf, geht es kürzer mit einer Methodenreferenz: String::length steht für s -> s.length(), Math::max für (a, b) -> Math.max(a, b).

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;

public class Main {
    public static void main(String[] args) {
        List<Integer> prices = new ArrayList<>(List.of(250, 399, 120));
        prices.replaceAll(p -> p + 50);
        System.out.println(prices);

        Predicate<String> isLong = w -> w.length() > 4;
        System.out.println(isLong.test("Java"));
        System.out.println(isLong.test("Lambda"));

        Function<String, String> shout = s -> s.toUpperCase() + "!";
        System.out.println(shout.apply("hello"));

        Function<String, Integer> length = String::length;
        System.out.println(length.apply("stream"));

        List<String> drinks = new ArrayList<>(List.of("tea", "coffee", "juice", "milk"));
        drinks.removeIf(d -> d.length() < 4);
        System.out.println(drinks);
    }
}

Streams

list.stream() macht aus einer Liste einen Strom von Elementen, durch den sie nacheinander laufen. Die Schritte hängst du mit Punkten aneinander:

  • filter(b -> ...) behält nur Elemente, für die die Bedingung gilt
  • map(Book::title) wandelt jedes Element um
  • distinct() entfernt Doppelte, sorted() sortiert

Am Ende steht ein Schritt, der ein Ergebnis liefert:

  • toList() sammelt in eine neue Liste
  • mapToInt(...) und danach sum() zählen zusammen
  • reduce(start, (a, b) -> ...) fasst alles zu einem Wert zusammen: Es beginnt bei start und verrechnet das bisherige Ergebnis a mit dem nächsten Element b

Die ursprüngliche Liste bleibt dabei unverändert.

import java.util.List;

record Book(String title, String genre, int pages) {
}

public class Main {
    public static void main(String[] args) {
        List<Book> books = List.of(
                new Book("Dune", "sci-fi", 600),
                new Book("Emma", "classic", 470),
                new Book("Neuromancer", "sci-fi", 270),
                new Book("Persuasion", "classic", 250),
                new Book("Hyperion", "sci-fi", 480));

        List<String> longBooks = books.stream()
                .filter(b -> b.pages() > 400)
                .map(Book::title)
                .toList();
        System.out.println(longBooks);

        int totalPages = books.stream().mapToInt(Book::pages).sum();
        System.out.println("Pages: " + totalPages);

        int maxPages = books.stream().map(Book::pages).reduce(0, Math::max);
        System.out.println("Longest: " + maxPages);

        List<String> genres = books.stream()
                .map(Book::genre)
                .distinct()
                .sorted()
                .toList();
        System.out.println(genres);
    }
}

Comparator und Gruppieren

Mit Comparable (Kapitel 9) hat eine Klasse eine feste Sortierung. Ein Comparator ist eine Sortierregel, die du von außen mitgibst:

  • Comparator.comparing(Player::points) sortiert aufsteigend nach Punkten
  • .reversed() dreht die Reihenfolge um
  • .thenComparing(Player::name) entscheidet bei Gleichstand

Du übergibst ihn an stream().sorted(...) oder an list.sort(...).

collect(Collectors.groupingBy(Player::team, ...)) verteilt die Elemente auf Gruppen und liefert eine Map: Team → Ergebnis. Der zweite Collector sagt, was pro Gruppe herauskommt, etwa Collectors.summingInt(...) für eine Summe oder Collectors.counting() für die Anzahl (als Long).

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

record Player(String name, String team, int points) {
}

public class Main {
    public static void main(String[] args) {
        List<Player> players = List.of(
                new Player("Mia", "red", 30),
                new Player("Leo", "blue", 45),
                new Player("Ana", "red", 45),
                new Player("Ben", "blue", 20));

        List<Player> ranking = players.stream()
                .sorted(Comparator.comparing(Player::points).reversed()
                        .thenComparing(Player::name))
                .toList();
        for (Player p : ranking) {
            System.out.println(p.name() + " " + p.points());
        }

        Map<String, Integer> perTeam = players.stream()
                .collect(Collectors.groupingBy(Player::team,
                        Collectors.summingInt(Player::points)));
        System.out.println("red: " + perTeam.get("red"));
        System.out.println("blue: " + perTeam.get("blue"));

        Map<String, Long> sizes = players.stream()
                .collect(Collectors.groupingBy(Player::team, Collectors.counting()));
        System.out.println("players in red: " + sizes.get("red"));
    }
}

Typische Fehler

  • Lokale Variable im Lambda ändern

    Ein Lambda darf lokale Variablen von außen lesen, aber nicht ändern:

    int sum = 0;
    prices.forEach(p -> sum += p);
    // local variables referenced from a lambda expression
    // must be final or effectively final

    Für Summen gibt es mapToInt(...).sum() oder eine normale Schleife.

  • reversed() an der falschen Stelle

    reversed() dreht alles um, was davor steht. comparing(Player::points).thenComparing(Player::name).reversed() sortiert deshalb auch die Namen rückwärts. Steht reversed() direkt hinter comparing(...), gilt es nur für die Punkte.

  • Ergebnis von toList() ändern

    Die Liste aus toList() und aus List.of(...) lässt sich nicht ändern. add bricht mit UnsupportedOperationException ab. Brauchst du eine änderbare Liste, kopiere sie: new ArrayList<>(list).

  • Ein Stream ist nach einem Durchlauf verbraucht

    Einen Stream kannst du nur einmal benutzen. Rufst du auf demselben Stream ein zweites Ergebnis ab, gibt es eine IllegalStateException. Für eine zweite Auswertung schreibst du einfach noch einmal list.stream().