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 Klammernw -> { ... return ...; }: mehrere Zeilen in geschweiften Klammern, mitreturn
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 mittest(x), liefertbooleanFunction<T, R>: wandelt einTin einRum, ausgeführt mitapply(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 giltmap(Book::title)wandelt jedes Element umdistinct()entfernt Doppelte,sorted()sortiert
Am Ende steht ein Schritt, der ein Ergebnis liefert:
toList()sammelt in eine neue ListemapToInt(...)und danachsum()zählen zusammenreduce(start, (a, b) -> ...)fasst alles zu einem Wert zusammen: Es beginnt beistartund verrechnet das bisherige Ergebnisamit dem nächsten Elementb
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 finalFü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. Stehtreversed()direkt hintercomparing(...), gilt es nur für die Punkte.Ergebnis von toList() ändern
Die Liste aus
toList()und ausList.of(...)lässt sich nicht ändern.addbricht mitUnsupportedOperationExceptionab. 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 einmallist.stream().