Skip to content
IRC-Coding IRC-Coding
Java Stream API Lambda Expressions Functional Interfaces Map Filter Reduce Collect

Java Stream API: Lambda, Functional Interfaces, Map, Filter, Reduce & Collect

Java Stream API mit Lambda-Ausdrücken und Functional Interfaces. Map, filter, reduce, collect mit praktischen Beispielen für funktionale Programmierung und Datenverarbeitung.

S

schutzgeist

2 min read

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) -> expression oder (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

InterfaceMethodDescriptionExample
Predicate<T>boolean test(T t)Test conditions -> s.length() > 5
Function<T,R>R apply(T t)Transforms -> s.toUpperCase()
Consumer<T>void accept(T t)ConsumeSystem.out::println
Supplier<T>T get()Supply() -> new Random()
UnaryOperator<T>T apply(T t)Unary operationx -> 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

  1. Was ist der Unterschied zwischen intermediate und terminal operations? Intermediate sind lazy und geben Stream zurück, terminal sind eager und beenden Verarbeitung.

  2. Erklären Sie Lambda-Ausdrücke! Anonyme Funktionen mit kompakter Syntax: (parameter) -> expression oder (parameter) -> { statements }.

  3. Wann verwendet man Methodenreferenzen? Als kürzere Alternative zu Lambda-Ausdrücken wenn eine Methode existiert, die genau passt.

  4. Was ist der Vorteil von parallel streams? Automatische parallele Verarbeitung auf Multi-Core-Systemen für bessere Performance.

Wichtigste Quellen

  1. https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
  2. https://docs.oracle.com/javase/tutorial/collections/streams/
  3. https://www.baeldung.com/java-8-streams
Zurück zum Blog
Share:

Ähnliche Beiträge