Java Stream API: Lambda, Functional Interfaces, Map, Filter, Reduce & Collect
Dieser Beitrag ist eine umfassende Anleitung zur Java Stream API – inklusive Lambda-Ausdrücken, Functional Interfaces, map, filter, reduce und collect mit praktischen Beispielen.
In a Nutshell
Java Stream API ermöglicht funktionale Datenverarbeitung mit Lambda-Ausdrücken. map transformiert, filter selektiert, reduce aggregiert und collect sammelt Ergebnisse in Containern.
Kompakte Fachbeschreibung
Java Stream API ist eine funktionale API zur Verarbeitung von Datenkollektionen. Sie unterstützt deklarative Programmierung mit Lambda-Ausdrücken und Functional Interfaces.
Wichtige Konzepte:
Lambda-Ausdrücke
- Syntax:
(parameter) -> expressionoder(parameter) -> { statements } - Typ-Inferenz: Typ wird aus Kontext abgeleitet
- Effektiv final: Variablen müssen final oder effektiv final sein
- Methodenreferenzen: Kürzere Schreibweise für Lambda-Ausdrücke
Functional Interfaces
- **Predicate<T>**: boolean test(T t) - Prüfung
- **Function<T,R>**: R apply(T t) - Transformation
- **Consumer<T>**: void accept(T t) - Konsumption
- **Supplier<T>**: T get() - Erzeugung
- **UnaryOperator<T>**: T apply(T t) - Unäre Operation
- **BinaryOperator<T>**: T apply(T t1, T t2) - Binäre Operation
```java
### Stream-Operationen
- **Intermediate**: map, filter, sorted, distinct, limit, skip
- **Terminal**: forEach, collect, reduce, count, anyMatch, allMatch
- **Short-circuiting**: findFirst, findAny, anyMatch, allMatch, noneMatch
## Prüfungsrelevante Stichpunkte
- **Stream API**: Funktionale Datenverarbeitung in Java 8+
- **Lambda-Ausdrücke**: Anonyme Funktionen mit kompakter Syntax
- **Functional Interfaces**: Schnittstellen mit einer abstrakten Methode
- **Map**: Transformation von Elementen
- **Filter**: Selektion basierend auf Prädikaten
- **Reduce**: Aggregation von Stream-Elementen
- **Collect**: Sammeln von Ergebnissen in Containern
- **IHK-relevant**: Modernes Java, funktionale Programmierung
## Kernkomponenten
1. **Lambda-Ausdrücke**: Kompakte Funktionsliterale
2. **Functional Interfaces**: Typisierte Funktionsdefinitionen
3. **Stream-Erzeugung**: Collections, Arrays, I/O, Generatoren
4. **Intermediate Operations**: Lazy, verkettbare Operationen
5. **Terminal Operations**: Eager, beenden Stream-Verarbeitung
6. **Collectors**: Spezialisierte Sammeloperationen
7. **Parallel Streams**: Parallele Datenverarbeitung
8. **Optional**: Null-sichere Container für Werte
## Praxisbeispiele
### 1. Grundlegende Stream-Operationen
```java
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class StreamGrundlagen {
public static void main(String[] args) {
System.out.println("=== Stream API Grundlagen ===");
// Datenquelle
List<String> namen = Arrays.asList("Alice", "Bob", "Charlie", "Diana", "Eve");
List<Integer> zahlen = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Filter Demo
filterDemo(namen, zahlen);
// Map Demo
mapDemo(namen, zahlen);
// Reduce Demo
reduceDemo(zahlen);
// Collect Demo
collectDemo(namen, zahlen);
// Methodenreferenzen
methodenreferenzenDemo();
}
private static void filterDemo(List<String> namen, List<Integer> zahlen) {
System.out.println("\n--- Filter Demo ---");
// Lambda-Ausdruck für Filter
List<String> langeNamen = namen.stream()
.filter(name -> name.length() > 4)
.collect(Collectors.toList());
System.out.println("Namen mit > 4 Buchstaben: " + langeNamen);
// Mehrere Filter
List<Integer> gefilterteZahlen = zahlen.stream()
.filter(zahl -> zahl % 2 == 0) // Gerade Zahlen
.filter(zahl -> zahl > 3) // Größer als 3
.collect(Collectors.toList());
System.out.println("Gerade Zahlen > 3: " + gefilterteZahlen);
// Komplexes Prädikat
Predicate<String> komplexesPraedikat = name ->
name.startsWith("A") && name.length() <= 5;
List<String> gefilterteNamen = namen.stream()
.filter(komplexesPraedikat)
.collect(Collectors.toList());
System.out.println("Namen mit 'A' und ≤5 Buchstaben: " + gefilterteNamen);
}
private static void mapDemo(List<String> namen, List<Integer> zahlen) {
System.out.println("\n--- Map Demo ---");
// String zu Integer (Länge)
List<Integer> namenslaengen = namen.stream()
.map(name -> name.length())
.collect(Collectors.toList());
System.out.println("Namenslängen: " + namenslaengen);
// Integer zu String (Quadrate)
List<String> quadrate = zahlen.stream()
.map(zahl -> zahl * zahl)
.map(quad -> "Quadrat: " + quad)
.collect(Collectors.toList());
System.out.println("Quadrate: " + quadrate);
// FlatMap für verschachtelte Strukturen
List<List<Integer>> verschachtelt = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5),
Arrays.asList(6, 7, 8, 9)
);
List<Integer> flach = verschachtelt.stream()
.flatMap(list -> list.stream())
.collect(Collectors.toList());
System.out.println("Flachgemacht: " + flach);
// Map mit Objekten
List<Person> personen = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 35)
);
List<String> personenInfo = personen.stream()
.map(person -> person.getName() + " (" + person.getAlter() + ")")
.collect(Collectors.toList());
System.out.println("Personen-Info: " + personenInfo);
}
private static void reduceDemo(List<Integer> zahlen) {
System.out.println("\n--- Reduce Demo ---");
// Summe mit Reduce
Optional<Integer> summe = zahlen.stream()
.reduce((a, b) -> a + b);
System.out.println("Summe: " + summe.orElse(0));
// Produkt mit Reduce
Optional<Integer> produkt = zahlen.stream()
.reduce((a, b) -> a * b);
System.out.println("Produkt: " + produkt.orElse(1));
// Maximum mit Reduce
Optional<Integer> maximum = zahlen.stream()
.reduce(Integer::max);
System.out.println("Maximum: " + maximum.orElse(0));
// Reduce mit Identitätswert
int summeMitIdentitaet = zahlen.stream()
.reduce(0, Integer::sum);
System.out.println("Summe mit Identität: " + summeMitIdentitaet);
// String-Verkettung
List<String> woerter = Arrays.asList("Java", "Stream", "API");
Optional<String> verkettet = woerter.stream()
.reduce((a, b) -> a + " " + b);
System.out.println("Verkettet: " + verkettet.orElse(""));
}
private static void collectDemo(List<String> namen, List<Integer> zahlen) {
System.out.println("\n--- Collect Demo ---");
// To List
List<String> grossgeschrieben = namen.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("Großgeschrieben: " + grossgeschrieben);
// To Set
Set<Integer> quadrate = zahlen.stream()
.map(zahl -> zahl * zahl)
.collect(Collectors.toSet());
System.out.println("Quadrate als Set: " + quadrate);
// To Map
Map<String, Integer> namenMap = namen.stream()
.collect(Collectors.toMap(
name -> name, // Key-Mapper
name -> name.length() // Value-Mapper
));
System.out.println("Namen-Map: " + namenMap);
// Grouping By
Map<Integer, List<String>> nachLaengeGruppiert = namen.stream()
.collect(Collectors.groupingBy(String::length));
System.out.println("Nach Länge gruppiert: " + nachLaengeGruppiert);
// Partitioning By
Map<Boolean, List<Integer>> geradeUngerade = zahlen.stream()
.collect(Collectors.partitioningBy(zahl -> zahl % 2 == 0));
System.out.println("Partitioniert: " + geradeUngerade);
// Joining
String namensliste = namen.stream()
.collect(Collectors.joining(", ", "[", "]"));
System.out.println("Namensliste: " + namensliste);
// Summarizing
IntSummaryStatistics statistik = zahlen.stream()
.collect(Collectors.summarizingInt(Integer::intValue));
System.out.println("Statistik: " + statistik);
}
private static void methodenreferenzenDemo() {
System.out.println("\n--- Methodenreferenzen Demo ---");
List<String> namen = Arrays.asList("alice", "bob", "charlie");
// Statische Methodenreferenz
List<String> grossgeschrieben = namen.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("Statische Referenz: " + grossgeschrieben);
// Instanzmethodenreferenz
List<Integer> laengen = namen.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println("Instanz-Referenz: " + laengen);
// Konstruktorreferenz
List<Person> personen = namen.stream()
.map(name -> new Person(name, 20 + name.length()))
.collect(Collectors.toList());
System.out.println("Konstruktor-Referenz: " +
personen.stream()
.map(Person::getName)
.collect(Collectors.toList()));
}
// Hilfsklasse
static class Person {
private String name;
private int alter;
public Person(String name, int alter) {
this.name = name;
this.alter = alter;
}
public String getName() { return name; }
public int getAlter() { return alter; }
}
}
2. Fortgeschrittene Stream-Operationen
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
public class FortgeschritteneStreams {
public static void main(String[] args) {
System.out.println("=== Fortgeschrittene Stream Operationen ===");
// Daten für Demonstrationen
List<Student> studenten = Arrays.asList(
new Student("Alice", "Informatik", 85, 3),
new Student("Bob", "Mathematik", 92, 2),
new Student("Charlie", "Informatik", 78, 4),
new Student("Diana", "Physik", 88, 1),
new Student("Eve", "Informatik", 95, 2),
new Student("Frank", "Mathematik", 73, 3)
);
// Sortierung
sortierungDemo(studenten);
// Limit und Skip
limitSkipDemo(studenten);
// Distinct
distinctDemo();
// Match-Operationen
matchDemo(studenten);
// Find-Operationen
findDemo(studenten);
// Optional-Handling
optionalDemo(studenten);
// Parallel Streams
parallelStreamDemo(studenten);
}
private static void sortierungDemo(List<Student> studenten) {
System.out.println("\n--- Sortierung Demo ---");
// Nach Note sortieren
List<Student> nachNote = studenten.stream()
.sorted(Comparator.comparing(Student::getNote))
.collect(Collectors.toList());
System.out.println("Nach Note aufsteigend:");
nachNote.forEach(s -> System.out.println(" " + s.getName() + ": " + s.getNote()));
// Nach Note absteigend
List<Student> nachNoteAbsteigend = studenten.stream()
.sorted(Comparator.comparing(Student::getNote).reversed())
.collect(Collectors.toList());
System.out.println("\nNach Note absteigend:");
nachNoteAbsteigend.forEach(s -> System.out.println(" " + s.getName() + ": " + s.getNote()));
// Mehrkriteriensortierung
List<Student> mehrkriterium = studenten.stream()
.sorted(Comparator
.comparing(Student::getFach)
.thenComparing(Student::getNote)
.thenComparing(Student::getName))
.collect(Collectors.toList());
System.out.println("\nNach Fach, Note, Name:");
mehrkriterium.forEach(s -> System.out.println(" " + s.getFach() + " - " +
s.getName() + ": " + s.getNote()));
}
private static void limitSkipDemo(List<Student> studenten) {
System.out.println("\n--- Limit und Skip Demo ---");
// Erste 3 Studenten
List<Student> ersteDrei = studenten.stream()
.limit(3)
.collect(Collectors.toList());
System.out.println("Erste 3 Studenten:");
ersteDrei.forEach(s -> System.out.println(" " + s.getName()));
// Überspringen der ersten 2
List<Student> nachUeberspringen = studenten.stream()
.skip(2)
.collect(Collectors.toList());
System.out.println("\nNach Überspringen der ersten 2:");
nachUeberspringen.forEach(s -> System.out.println(" " + s.getName()));
// Pagination (Seite 2, 2 Elemente pro Seite)
int seite = 2;
int groesse = 2;
List<Student> paginiert = studenten.stream()
.skip((seite - 1) * groesse)
.limit(groesse)
.collect(Collectors.toList());
System.out.println("\nSeite " + seite + " (Größe " + groesse + "):");
paginiert.forEach(s -> System.out.println(" " + s.getName()));
}
private static void distinctDemo() {
System.out.println("\n--- Distinct Demo ---");
List<Integer> zahlenMitDuplikaten = Arrays.asList(1, 2, 2, 3, 4, 4, 4, 5, 1);
List<Integer> eindeutigeZahlen = zahlenMitDuplikaten.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("Mit Duplikaten: " + zahlenMitDuplikaten);
System.out.println("Eindeutig: " + eindeutigeZahlen);
// Distinct mit Objekten
List<String> faecher = Arrays.asList("Informatik", "Mathematik", "Informatik",
"Physik", "Mathematik", "Informatik");
List<String> eindeutigeFaecher = faecher.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("\nFächer mit Duplikaten: " + faecher);
System.out.println("Eindeutige Fächer: " + eindeutigeFaecher);
}
private static void matchDemo(List<Student> studenten) {
System.out.println("\n--- Match Demo ---");
// All Match - alle erfüllen Bedingung
boolean alleBestanden = studenten.stream()
.allMatch(s -> s.getNote() >= 50);
System.out.println("Alle bestanden: " + alleBestanden);
boolean alleInformatik = studenten.stream()
.allMatch(s -> s.getFach().equals("Informatik"));
System.out.println("Alle Informatik: " + alleInformatik);
// Any Match - mindestens einer erfüllt Bedingung
boolean jemandInformatik = studenten.stream()
.anyMatch(s -> s.getFach().equals("Informatik"));
System.out.println("Jemand Informatik: " + jemandInformatik);
boolean jemandSehrGut = studenten.stream()
.anyMatch(s -> s.getNote() >= 90);
System.out.println("Jemand sehr gut: " + jemandSehrGut);
// None Match - keiner erfüllt Bedingung
boolean niemandDurchgefallen = studenten.stream()
.noneMatch(s -> s.getNote() < 50);
System.out.println("Niemand durchgefallen: " + niemandDurchgefallen);
}
private static void findDemo(List<Student> studenten) {
System.out.println("\n--- Find Demo ---");
// Find First - erstes Element
Optional<Student> erster = studenten.stream()
.findFirst();
erster.ifPresent(s -> System.out.println("Erster Student: " + s.getName()));
// Find Any - irgendein Element (besonders bei Parallel Streams)
Optional<Student> irgendeinInformatiker = studenten.stream()
.filter(s -> s.getFach().equals("Informatik"))
.findAny();
irgendeinInformatiker.ifPresent(s ->
System.out.println("Irgendein Informatiker: " + s.getName()));
// Find mit komplexem Prädikat
Optional<Student> besterMathematiker = studenten.stream()
.filter(s -> s.getFach().equals("Mathematik"))
.max(Comparator.comparing(Student::getNote));
besterMathematiker.ifPresent(s ->
System.out.println("Bester Mathematiker: " + s.getName() + " (" + s.getNote() + ")"));
}
private static void optionalDemo(List<Student> studenten) {
System.out.println("\n--- Optional Handling Demo ---");
// Optional mit Map
Optional<String> ersterName = studenten.stream()
.findFirst()
.map(Student::getName);
ersterName.ifPresent(name -> System.out.println("Erster Name: " + name));
// Optional mit Filter
Optional<Student> besterStudent = studenten.stream()
.max(Comparator.comparing(Student::getNote));
String besterName = besterStudent
.filter(s -> s.getNote() >= 90)
.map(Student::getName)
.orElse("Keiner mit 90+ Punkten");
System.out.println("Bester Student (90+): " + besterName);
// Optional Chaining
Optional<String> fachDesBesten = studenten.stream()
.max(Comparator.comparing(Student::getNote))
.flatMap(s -> Optional.ofNullable(s.getFach()))
.map(String::toUpperCase);
fachDesBesten.ifPresent(fach ->
System.out.println("Fach des Besten: " + fach));
// Optional mit Supplier
String defaultValue = studenten.stream()
.filter(s -> s.getName().equals("NichtExistent"))
.findFirst()
.map(Student::getName)
.orElseGet(() -> "Standard-Student");
System.out.println("Default Wert: " + defaultValue);
}
private static void parallelStreamDemo(List<Student> studenten) {
System.out.println("\n--- Parallel Stream Demo ---");
// Parallele Verarbeitung
long startZeit = System.currentTimeMillis();
List<String> namenParallel = studenten.parallelStream()
.filter(s -> s.getNote() > 80)
.map(Student::getName)
.sorted()
.collect(Collectors.toList());
long endZeit = System.currentTimeMillis();
System.out.println("Parallel Ergebnis: " + namenParallel);
System.out.println("Parallel Zeit: " + (endZeit - startZeit) + "ms");
// Vergleich mit sequentieller Verarbeitung
startZeit = System.currentTimeMillis();
List<String> namenSequentiell = studenten.stream()
.filter(s -> s.getNote() > 80)
.map(Student::getName)
.sorted()
.collect(Collectors.toList());
endZeit = System.currentTimeMillis();
System.out.println("\nSequentiell Ergebnis: " + namenSequentiell);
System.out.println("Sequentiell Zeit: " + (endZeit - startZeit) + "ms");
// Thread-Info bei Parallel Stream
System.out.println("\nThread-Info bei Parallel Stream:");
studenten.parallelStream()
.forEach(s -> System.out.println(s.getName() + " auf " +
Thread.currentThread().getName()));
}
// Student-Klasse
static class Student {
private String name;
private String fach;
private int note;
private int semester;
public Student(String name, String fach, int note, int semester) {
this.name = name;
this.fach = fach;
this.note = note;
this.semester = semester;
}
public String getName() { return name; }
public String getFach() { return fach; }
public int getNote() { return note; }
public int getSemester() { return semester; }
}
}
3. Spezialisierte Collectors und Custom Operations
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
public class SpezialisierteCollectors {
public static void main(String[] args) {
System.out.println("=== Spezialisierte Collectors Demo ===");
// Testdaten
List<Produkt> produkte = Arrays.asList(
new Produkt("Laptop", "Elektronik", 999.99, 5),
new Produkt("Maus", "Elektronik", 29.99, 15),
new Produkt("Tastatur", "Elektronik", 79.99, 8),
new Produkt("Buch", "Bücher", 19.99, 20),
new Produkt("Stift", "Büro", 2.99, 50),
new Produkt("Papier", "Büro", 9.99, 30)
);
// Gruppierung mit Aggregation
gruppierungMitAggregation(produkte);
// Multi-Level Gruppierung
multiLevelGruppierung(produkte);
// Custom Collector
customCollectorDemo();
// Downstream Collectors
downstreamCollectorsDemo(produkte);
// Primitive Streams
primitiveStreamsDemo();
}
private static void gruppierungMitAggregation(List<Produkt> produkte) {
System.out.println("\n--- Gruppierung mit Aggregation ---");
// Gruppierung nach Kategorie mit Statistiken
Map<String, DoubleSummaryStatistics> preisStatistik = produkte.stream()
.collect(Collectors.groupingBy(
Produkt::getKategorie,
Collectors.summarizingDouble(Produkt::getPreis)
));
preisStatistik.forEach((kategorie, statistik) -> {
System.out.println(kategorie + ":");
System.out.println(" Durchschnitt: " + statistik.getAverage());
System.out.println(" Minimum: " + statistik.getMin());
System.out.println(" Maximum: " + statistik.getMax());
System.out.println(" Summe: " + statistik.getSum());
});
// Gruppierung mit Mapping
Map<String, Set<String>> kategorieNamen = produkte.stream()
.collect(Collectors.groupingBy(
Produkt::getKategorie,
Collectors.mapping(Produkt::getName, Collectors.toSet())
));
System.out.println("\nKategorien mit Produktnamen:");
kategorieNamen.forEach((kategorie, namen) ->
System.out.println(kategorie + ": " + namen));
// Gruppierung mit Filter
Map<String, List<Produkt>> teureProdukte = produkte.stream()
.collect(Collectors.groupingBy(
Produkt::getKategorie,
Collectors.filtering(p -> p.getPreis() > 50, Collectors.toList())
));
System.out.println("\nTeure Produkte (>50€):");
teureProdukte.forEach((kategorie, produkteListe) -> {
if (!produkteListe.isEmpty()) {
System.out.println(kategorie + ": " +
produkteListe.stream().map(Produkt::getName).collect(Collectors.toList()));
}
});
}
private static void multiLevelGruppierung(List<Produkt> produkte) {
System.out.println("\n--- Multi-Level Gruppierung ---");
// Produkte nach Preis-Kategorien gruppieren
Map<String, Map<String, List<Produkt>>> multiLevel = produkte.stream()
.collect(Collectors.groupingBy(
p -> p.getPreis() < 50 ? "Günstig" : "Teuer",
Collectors.groupingBy(Produkt::getKategorie)
));
System.out.println("Multi-Level Gruppierung:");
multiLevel.forEach((preisKategorie, kategorieMap) -> {
System.out.println(preisKategorie + ":");
kategorieMap.forEach((kategorie, produkteListe) -> {
System.out.println(" " + kategorie + ": " +
produkteListe.stream().map(Produkt::getName).collect(Collectors.toList()));
});
});
}
private static void customCollectorDemo() {
System.out.println("\n--- Custom Collector Demo ---");
List<String> woerter = Arrays.asList("Java", "Stream", "API", "Functional", "Programming");
// Custom Collector für String-Verkettung mit Trennzeichen und Präfix/Suffix
Collector<String, StringBuilder, String> customStringCollector = Collector.of(
StringBuilder::new, // Supplier
(builder, str) -> { // Accumulator
if (builder.length() > 0) {
builder.append(" | ");
}
builder.append(str.toUpperCase());
},
StringBuilder::append, // Combiner
StringBuilder::toString, // Finisher
Characteristics.IDENTITY_FINISH
);
String ergebnis = woerter.stream().collect(customStringCollector);
System.out.println("Custom Collector Ergebnis: " + ergebnis);
// Custom Collector für Statistik
Collector<Integer, int[], Double> averageCollector = Collector.of(
() -> new int[2], // [sum, count]
(acc, num) -> {
acc[0] += num; // sum
acc[1]++; // count
},
(acc1, acc2) -> {
acc1[0] += acc2[0];
acc1[1] += acc2[1];
return acc1;
},
acc -> acc[1] == 0 ? 0 : (double) acc[0] / acc[1] // average
);
List<Integer> zahlen = Arrays.asList(10, 20, 30, 40, 50);
double durchschnitt = zahlen.stream().collect(averageCollector);
System.out.println("Custom Average: " + durchschnitt);
}
private static void downstreamCollectorsDemo(List<Produkt> produkte) {
System.out.println("\n--- Downstream Collectors Demo ---");
// GroupingBy mit Counting
Map<String, Long> anzahlProKategorie = produkte.stream()
.collect(Collectors.groupingBy(
Produkt::getKategorie,
Collectors.counting()
));
System.out.println("Anzahl pro Kategorie:");
anzahlProKategorie.forEach((kategorie, anzahl) ->
System.out.println(kategorie + ": " + anzahl));
// GroupingBy mit Summing
Map<String, Integer> lagerProKategorie = produkte.stream()
.collect(Collectors.groupingBy(
Produkt::getKategorie,
Collectors.summingInt(Produkt::getLagerbestand)
));
System.out.println("\nLagerbestand pro Kategorie:");
lagerProKategorie.forEach((kategorie, bestand) ->
System.out.println(kategorie + ": " + bestand));
// GroupingBy mit maxBy
Map<String, Optional<Produkt>> teuerstesProKategorie = produkte.stream()
.collect(Collectors.groupingBy(
Produkt::getKategorie,
Collectors.maxBy(Comparator.comparing(Produkt::getPreis))
));
System.out.println("\nTeuerstes Produkt pro Kategorie:");
teuerstesProKategorie.forEach((kategorie, optional) ->
optional.ifPresent(p -> System.out.println(kategorie + ": " + p.getName())));
// CollectingAndThen für nachbearbeitete Ergebnisse
Map<String, String> kategorieInfo = produkte.stream()
.collect(Collectors.groupingBy(
Produkt::getKategorie,
Collectors.collectingAndThen(
Collectors.toList(),
liste -> liste.size() + " Produkte, " +
String.format("%.2f€",
liste.stream().mapToDouble(Produkt::getPreis).average().orElse(0))
)
));
System.out.println("\nKategorie-Info:");
kategorieInfo.forEach((kategorie, info) -> System.out.println(kategorie + ": " + info));
}
private static void primitiveStreamsDemo() {
System.out.println("\n--- Primitive Streams Demo ---");
// IntStream
IntStream zahlen = IntStream.range(1, 10);
int summe = zahlen.sum();
System.out.println("Summe 1-9: " + summe);
// Mit Boxed zu Objekt-Stream
List<Integer> zahlenListe = IntStream.rangeClosed(1, 5)
.boxed()
.collect(Collectors.toList());
System.out.println("Zahlen als List: " + zahlenListe);
// DoubleStream mit Berechnungen
double[] preise = {19.99, 29.99, 99.99, 149.99};
DoubleSummaryStatistics preisStatistik = Arrays.stream(preise)
.summaryStatistics();
System.out.println("\nPreis-Statistik:");
System.out.println(" Anzahl: " + preisStatistik.getCount());
System.out.println(" Summe: " + preisStatistik.getSum());
System.out.println(" Durchschnitt: " + preisStatistik.getAverage());
System.out.println(" Min: " + preisStatistik.getMin());
System.out.println(" Max: " + preisStatistik.getMax());
// LongStream für große Zahlen
long fakultaet = LongStream.rangeClosed(1, 10)
.reduce(1, (a, b) -> a * b);
System.out.println("\n10! = " + fakultaet);
// Primitive Stream mit Filter
long geradeZahlen = IntStream.rangeClosed(1, 20)
.filter(n -> n % 2 == 0)
.count();
System.out.println("Gerade Zahlen 1-20: " + geradeZahlen);
// MapToObj für Transformation
List<String> zahlenAlsStrings = IntStream.rangeClosed(1, 5)
.mapToObj(n -> "Zahl " + n)
.collect(Collectors.toList());
System.out.println("Zahlen als Strings: " + zahlenAlsStrings);
}
// Produkt-Klasse
static class Produkt {
private String name;
private String kategorie;
private double preis;
private int lagerbestand;
public Produkt(String name, String kategorie, double preis, int lagerbestand) {
this.name = name;
this.kategorie = kategorie;
this.preis = preis;
this.lagerbestand = lagerbestand;
}
public String getName() { return name; }
public String getKategorie() { return kategorie; }
public double getPreis() { return preis; }
public int getLagerbestand() { return lagerbestand; }
}
}
Functional Interfaces Übersicht
| Interface | Method | Description | Example |
|---|---|---|---|
Predicate<T> | boolean test(T t) | Test condition | s -> s.length() > 5 |
Function<T,R> | R apply(T t) | Transform | s -> s.toUpperCase() |
Consumer<T> | void accept(T t) | Consume | System.out::println |
Supplier<T> | T get() | Supply | () -> new Random() |
UnaryOperator<T> | T apply(T t) | Unary operation | x -> x * x |
BinaryOperator<T> | T apply(T t1, T t2) | Binary operation | (a, b) -> a + b |
Stream-Operationen Übersicht
Intermediate Operations (Lazy)
// Filter
stream.filter(x -> x > 0)
// Map
stream.map(x -> x * 2)
// FlatMap
stream.flatMap(list -> list.stream())
// Sorted
stream.sorted()
stream.sorted(Comparator.reverseOrder())
// Distinct
stream.distinct()
// Limit/Skip
stream.limit(10)
stream.skip(5)
// Peek (for debugging)
stream.peek(System.out::println)
Terminal Operations (Eager)
// ForEach
stream.forEach(System.out::println)
// Collect
stream.collect(Collectors.toList())
// Reduce
stream.reduce((a, b) -> a + b)
// Count
stream.count()
// Min/Max
stream.min(Comparator.naturalOrder())
stream.max(Comparator.reverseOrder())
// Match
stream.anyMatch(x -> x > 0)
stream.allMatch(x -> x > 0)
stream.noneMatch(x -> x > 0)
// Find
stream.findFirst()
stream.findAny()
Methodenreferenzen Typen
Statische Methodenreferenz
// Lambda: s -> Integer.parseInt(s)
// Methodenreferenz: Integer::parseInt
list.stream().map(Integer::parseInt)
Instanzmethodenreferenz
// Lambda: s -> s.toUpperCase()
// Methodenreferenz: String::toUpperCase
list.stream().map(String::toUpperCase)
Konstruktorreferenz
// Lambda: name -> new Person(name)
// Methodenreferenz: Person::new
list.stream().map(Person::new)
Performance-Überlegungen
Wann Streams verwenden
- Komplexe Datenverarbeitung: Filter, Map, Reduce Operationen
- Lesbarkeit: Deklarativer Code statt imperativer Schleifen
- Parallelisierung: Einfache Umstellung auf parallele Verarbeitung
- Funktionale Programmierung: Unveränderliche Datenstrukturen
Wann Streams vermeiden
- Einfache Operationen: Traditionelle Schleifen sind oft schneller
- Performance-kritischer Code: Stream-Overhead kann relevant sein
- Primitive Arrays: Spezialisierte Operationen oft besser
- Sehr kleine Collections: Overhead überwiegt Nutzen
Vorteile und Nachteile
Vorteile von Stream API
- Lesbarkeit: Deklarative, ausdrucksstarke Syntax
- Komposition: Leicht verkettbare Operationen
- Parallelisierung: Einfache Umstellung auf parallele Verarbeitung
- Funktional: Unterstützung funktionaler Programmierung
- Lazy Evaluation: Effiziente Verarbeitung
Nachteile
- Performance: Overhead bei einfachen Operationen
- Debugging: Schwieriger als imperative Schleifen
- Lernkurve: Neue Konzepte und Syntax
- Speicher: Intermediate Collections können Speicher verbrauchen
Häufige Prüfungsfragen
-
Was ist der Unterschied zwischen intermediate und terminal operations? Intermediate sind lazy und geben Stream zurück, terminal sind eager und beenden Verarbeitung.
-
Erklären Sie Lambda-Ausdrücke! Anonyme Funktionen mit kompakter Syntax:
(parameter) -> expressionoder(parameter) -> { statements }. -
Wann verwendet man Methodenreferenzen? Als kürzere Alternative zu Lambda-Ausdrücken wenn eine Methode existiert, die genau passt.
-
Was ist der Vorteil von parallel streams? Automatische parallele Verarbeitung auf Multi-Core-Systemen für bessere Performance.
Wichtigste Quellen
- https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
- https://docs.oracle.com/javase/tutorial/collections/streams/
- https://www.baeldung.com/java-8-streams