Kurz: Ein Lambda ist eine kurze, namenlose Funktion. Sie passt überall, wo ein funktionales Interface (genau eine abstrakte Methode) erwartet wird:
Teil des Kurses Java
Lambda-Ausdrücke
Ein Lambda ist eine kurze, namenlose Funktion. Sie passt überall, wo ein funktionales Interface (genau eine abstrakte Methode) erwartet wird:
import java.util.*;
import java.util.function.*;
public class Lambdas {
public static void main(String[] args) {
Function<Integer, Integer> quadrat = x -> x * x;
BiFunction<Integer, Integer, Integer> plus = (a, b) -> a + b;
Predicate<String> leer = s -> s.isEmpty();
Supplier<String> lieferant = () -> "Wert";
Consumer<String> ausgabe = s -> System.out.println("> " + s);
UnaryOperator<String> gross = String::toUpperCase; // Methodenreferenz
System.out.println(quadrat.apply(7) + " " + plus.apply(3, 4) + " " + leer.test("") + " " + lieferant.get());
ausgabe.accept(gross.apply("hallo"));
System.out.println(quadrat.andThen(x -> x + 1).apply(5)); // verketten
System.out.println(leer.negate().test("x"));
List<String> namen = new ArrayList<>(List.of("Zoe", "Anna", "Tom"));
namen.sort((a, b) -> a.length() - b.length());
namen.forEach(n -> System.out.print(n + " "));
System.out.println();
}
}Ausgabe:
49 7 true Wert
> HALLO
26
true
Zoe Tom Anna| Interface | Methode | Bedeutung |
|---|---|---|
Function<T, R> | R apply(T) | wandelt um |
Predicate<T> | boolean test(T) | prüft |
Consumer<T> | void accept(T) | verarbeitet |
Supplier<T> | T get() | liefert |
BiFunction<T, U, R> | R apply(T, U) | zwei Eingaben |
UnaryOperator<T>, BinaryOperator<T> | gleiche Typen | |
Runnable, Callable<V> | run(), call() | Aufgaben |
Methodenreferenzen sind Kurzformen: String::length, System.out::println, Person::new, this::methode.
Streams
Ein Stream beschreibt eine Pipeline für Daten: Quelle → Zwischenoperationen → Endoperation. Das Original bleibt unverändert.
import java.util.*;
import java.util.stream.*;
public class Pipeline {
record Person(String name, int alter, String stadt) {}
public static void main(String[] args) {
List<Person> leute = List.of(
new Person("Mia", 17, "Köln"), new Person("Tom", 19, "Berlin"),
new Person("Zoe", 21, "Köln"), new Person("Ben", 15, "Bonn"));
List<String> volljaehrig = leute.stream()
.filter(p -> p.alter() >= 18)
.map(Person::name)
.sorted()
.collect(Collectors.toList());
System.out.println(volljaehrig);
double schnitt = leute.stream().mapToInt(Person::alter).average().orElse(0);
System.out.println(schnitt + " " + leute.stream().mapToInt(Person::alter).max().getAsInt());
System.out.println(leute.stream().anyMatch(p -> p.stadt().equals("Bonn")) + " " +
leute.stream().allMatch(p -> p.alter() > 16) + " " +
leute.stream().filter(p -> p.alter() > 100).findFirst().isPresent());
System.out.println(IntStream.rangeClosed(1, 5).boxed().collect(Collectors.toList()));
System.out.println(Stream.of("a", "b", "c").collect(Collectors.joining(", ", "[", "]")));
}
}Ausgabe:
[Tom, Zoe]
18.0 21
true false false
[1, 2, 3, 4, 5]
[a, b, c]Typische Operationen
| Zwischenoperation | Wirkung |
|---|---|
filter(p) | behält passende Elemente |
map(f) | wandelt jedes Element um |
flatMap(f) | wandelt und flacht ein |
sorted(), sorted(cmp) | sortiert |
distinct() | entfernt Duplikate |
limit(n), skip(n) | begrenzt / überspringt |
peek(c) | Blick hinein (zum Debuggen) |
| Endoperation | Wirkung |
|---|---|
collect(...) | sammelt in Liste, Set, Map, … |
forEach(c) | wendet an |
count(), sum(), average(), min, max | Auswertungen |
reduce(init, op) | faltet zu einem Wert |
anyMatch, allMatch, noneMatch | prüfen |
findFirst(), findAny() | ein Element (Optional) |
toList() | unveränderliche Liste (ab Java 16) |
Gruppieren und Zusammenfassen
Collectors bietet mächtige Auswertungen:
import java.util.*;
import java.util.stream.*;
public class Gruppen {
record Person(String name, int alter, String stadt) {}
public static void main(String[] args) {
List<Person> leute = List.of(
new Person("Mia", 17, "Köln"), new Person("Tom", 19, "Berlin"),
new Person("Zoe", 21, "Köln"), new Person("Ben", 15, "Bonn"));
Map<String, List<String>> nachStadt = leute.stream()
.collect(Collectors.groupingBy(Person::stadt, TreeMap::new, Collectors.mapping(Person::name, Collectors.toList())));
System.out.println(nachStadt);
Map<String, Long> anzahl = leute.stream().collect(Collectors.groupingBy(Person::stadt, TreeMap::new, Collectors.counting()));
System.out.println(anzahl);
Map<Boolean, List<String>> geteilt = leute.stream()
.collect(Collectors.partitioningBy(p -> p.alter() >= 18, Collectors.mapping(Person::name, Collectors.toList())));
System.out.println(geteilt);
Map<String, Integer> alterMap = leute.stream().collect(Collectors.toMap(Person::name, Person::alter));
System.out.println(new TreeMap<>(alterMap));
IntSummaryStatistics st = leute.stream().mapToInt(Person::alter).summaryStatistics();
System.out.println(st.getMin() + " " + st.getMax() + " " + st.getAverage() + " " + st.getSum());
System.out.println(leute.stream().map(Person::alter).reduce(0, Integer::sum));
System.out.println(leute.stream().sorted(Comparator.comparingInt(Person::alter).reversed()).map(Person::name).toList());
}
}Ausgabe:
{Berlin=[Tom], Bonn=[Ben], Köln=[Mia, Zoe]}
{Berlin=1, Bonn=1, Köln=2}
{false=[Mia, Ben], true=[Tom, Zoe]}
{Ben=15, Mia=17, Tom=19, Zoe=21}
15 21 18.0 72
72
[Zoe, Tom, Mia, Ben]Streams sind lazy
Zwischenoperationen laufen erst, wenn eine Endoperation sie anstößt. Das erlaubt auch unendliche Streams:
import java.util.stream.*;
public class Faul {
public static void main(String[] args) {
var ergebnis = Stream.iterate(1, n -> n * 2) // 1, 2, 4, 8, ... unendlich
.peek(n -> System.out.print("[" + n + "]"))
.filter(n -> n > 10)
.limit(2)
.toList();
System.out.println();
System.out.println(ergebnis);
System.out.println(IntStream.range(0, 20).filter(n -> n % 3 == 0).sum());
}
}Ausgabe:
[1][2][4][8][16][32]
[16, 32]
63Hinweis
Ein Stream lässt sich nur einmal benutzen. Für Rechenarbeit auf Listen sind Streams bequem, eine einfache
for-Schleife ist manchmal klarer und schneller. Entscheide nach Lesbarkeit.
Parallele Streams
list.parallelStream() verteilt die Arbeit auf mehrere Kerne. Sinnvoll nur bei vielen Elementen und rechenintensiven, nebenwirkungsfreien Schritten. Messen, bevor du es einsetzt.
Merke
- Lambdas
(a, b) -> a + bund MethodenreferenzenString::lengthersetzen kleine Klassen - Funktionale Interfaces:
Function,Predicate,Consumer,Supplier - Stream = Quelle →
filter/map/sorted→ Endoperation (collect,count,reduce) Collectors.groupingBy,partitioningBy,toMap,joiningfür Auswertungen- Streams sind lazy und einmal verwendbar
Übungsaufgabe
Gegeben eine Liste von Bestellungen (Kunde, Betrag): Berechne mit Streams den Umsatz je Kunde und gib die Top 3 aus.
Quiz zur Selbstkontrolle
Was ist ein funktionales Interface?
- Ein Interface mit genau einer abstrakten Methode (richtig)
- Ein Interface ohne Methoden
- Ein Interface für Funktionen in C
- Ein privates Interface
Wann werden Zwischenoperationen eines Streams ausgeführt?
- Erst, wenn eine Endoperation sie anstößt (richtig)
- Sofort
- Beim Erzeugen des Streams
- Nie
Wofür steht collect(Collectors.toList())?
- Die Elemente des Streams in einer Liste sammeln (richtig)
- Die Liste löschen
- Den Stream starten
- Die Liste sortieren
Weiter im Kurs
Zurück: Generics
Weiter: Dateien und Ein-/Ausgabe
Alle Kapitel: Java im Überblick