Lambda ·
Funktionale Interfaces ·
Stream API ·
Intermediate/ Terminal Operations ·
Collectors ·
Parallele Streams
Mit Java 8 wurden Lambdas und die Stream-API eingeführt – sie ermöglichen funktionale, deklarative Datenverarbeitung und machen Code lesbarer und prägnanter.
Lambda-Ausdrücke sind anonyme Funktionen, die als Parameter übergeben werden können. Sie sind die Grundlage für funktionale Programmierung in Java.
| Form | Beschreibung | Beispiel |
|---|---|---|
| (param) -> ausdruck | Einzelner Ausdruck (impliziter return) | x -> x * 2 |
| (param) -> { ... } | Block mit mehreren Anweisungen | name -> { System.out.println(name); } |
| (p1, p2) -> ausdruck | Mehrere Parameter | (a, b) -> a + b |
| () -> ausdruck | Keine Parameter | () -> Math.random() |
| param -> ausdruck | Klammern optional bei einem Parameter | x -> x * x |
x -> x * 2. Bei mehreren Parametern oder ohne Parameter sind Klammern erforderlich.
Funktionale Interfaces haben genau eine abstrakte Methode. Sie sind die Grundlage für Lambdas und werden mit @FunctionalInterface annotiert.
| Interface | Methode | Beschreibung |
|---|---|---|
| Predicate<T> | boolean test(T t) | Prüft eine Bedingung (true/false) |
| Function<T,R> | R apply(T t) | Transformiert T → R |
| Consumer<T> | void accept(T t) | Verarbeitet T (kein Rückgabewert) |
| Supplier<T> | T get() | Liefert ein T (kein Parameter) |
| BiFunction<T,U,R> | R apply(T t, U u) | Transformiert T,U → R |
| BiPredicate<T,U> | boolean test(T t, U u) | Prüft Bedingung mit 2 Parametern |
| BiConsumer<T,U> | void accept(T t, U u) | Verarbeitet T und U |
| UnaryOperator<T> | T apply(T t) | Function T → T (gleicher Typ) |
| BinaryOperator<T> | T apply(T t1, T t2) | BiFunction T,T → T |
@FunctionalInterface definiert werden – der Compiler prüft dann, dass es nur eine abstrakte Methode gibt.
Streams sind Sequenzen von Elementen, die mit funktionalen Operationen verarbeitet werden. Sie sind lazy (werden erst bei terminaler Operation ausgeführt) und nicht veränderbar (die Quelle bleibt unverändert).
| Quelle | Code |
|---|---|
| Liste / Collection | list.stream() |
| Array | Arrays.stream(array) |
| Werte (varargs) | Stream.of("a", "b", "c") |
| Unendlicher Stream | Stream.iterate(0, n -> n + 1) |
| Zufallszahlen | new Random().ints(10) |
| Datei-Zeilen | Files.lines(path) |
Intermediate-Operationen sind lazy und geben einen neuen Stream zurück. Terminal-Operationen starten die Verarbeitung und liefern ein Ergebnis oder einen Seiteneffekt.
| Typ | Operation | Beschreibung |
|---|---|---|
| Intermediate | filter(Predicate) | Filtert Elemente |
| Intermediate | map(Function) | Transformiert jedes Element |
| Intermediate | flatMap(Function) | Transformiert zu Stream und flacht ab |
| Intermediate | sorted() / sorted(Comparator) | Sortiert die Elemente |
| Intermediate | distinct() | Entfernt Duplikate |
| Intermediate | limit(n) | Begrenzt auf n Elemente |
| Intermediate | skip(n) | Überspringt n Elemente |
| Intermediate | peek(Consumer) | Führt Aktion für jedes Element aus (Debugging) |
| Terminal | forEach(Consumer) | Führt Aktion für jedes Element aus |
| Terminal | collect(Collector) | Sammelt Elemente in einer Collection |
| Terminal | reduce(BinaryOperator) | Reduziert auf einen einzelnen Wert |
| Terminal | count() | Zählt Elemente |
| Terminal | anyMatch(Predicate) | Prüft, ob mindestens eines zutrifft |
| Terminal | allMatch(Predicate) | Prüft, ob alle zutreffen |
| Terminal | findFirst() | Gibt erstes Element zurück |
Collectors sammeln die Elemente eines Streams in verschiedenen Datenstrukturen – Listen, Sets, Maps oder Strings.
| Collector | Beschreibung | Beispiel |
|---|---|---|
| toList() | Sammelt in eine List | .collect(Collectors.toList()) |
| toSet() | Sammelt in ein Set (Duplikate entfernt) | .collect(Collectors.toSet()) |
| toMap(keyMapper, valueMapper) | Sammelt in eine Map | .collect(Collectors.toMap(s -> s, s -> s.length())) |
| joining(delimiter) | Fügt Strings mit Trennzeichen zusammen | .collect(Collectors.joining(", ")) |
| groupingBy(classifier) | Gruppiert nach einem Schlüssel | .collect(Collectors.groupingBy(s -> s.length())) |
| counting() | Zählt Elemente einer Gruppe | .collect(Collectors.groupingBy(s -> s.length(), counting())) |
| summingInt() | Summiert int-Werte | .collect(Collectors.summingInt(s -> s.length())) |
| averagingInt() | Durchschnitt von int-Werten | .collect(Collectors.averagingInt(s -> s.length())) |
Collectors.groupingBy() ist extrem mächtig – es kann mit nachgeschalteten Collectors kombiniert werden, um komplexe Gruppierungen und Aggregationen durchzuführen.
Parallele Streams nutzen den Fork/Join-Pool zur parallelen Verarbeitung von Daten. Sie können die Leistung bei großen Datenmengen deutlich verbessern.
| Aspekt | Beschreibung |
|---|---|
| Erstellung | collection.parallelStream() oder stream().parallel() |
| Thread-Pool | ForkJoinPool.commonPool() (kann angepasst werden) |
| Thread-sicher | Operationen müssen threadsicher oder unabhängig sein |
| Ordnung | forEach() ist nicht geordnet, forEachOrdered() bewahrt Ordnung |
| Overhead | Nur sinnvoll bei großen Datenmengen oder CPU-intensiven Operationen |
forEach mit nicht-threadsicherem Code) können zu Fehlern führen!
->
Predicate
stream()
filter()
collect()
parallel
list.stream().filter(x -> x > 5).map(x -> x * 2).collect(Collectors.toList())