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

Java Stream API: Lambda, Functional Interfaces & Collect

Master Java Stream API with lambda expressions and functional interfaces. Learn map, filter, reduce, and collect with practical examples.

S

schutzgeist

2 min read
Java Stream API: Lambda, Functional Interfaces & Collect

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

This post is a comprehensive guide to the Java Stream API – including Lambda expressions, Functional Interfaces, map, filter, reduce and collect with practical examples.

In a Nutshell

Java Stream API enables functional data processing with Lambda expressions. map transforms, filter selects, reduce aggregates and collect gathers results in containers.

Compact Technical Description

Java Stream API is a functional API for processing data collections. It supports declarative programming with Lambda expressions and Functional Interfaces.

Important Concepts:

Lambda Expressions

  • Syntax: (parameter) -> expression or (parameter) -> { statements }
  • Type Inference: Type is inferred from context
  • Effectively Final: Variables must be final or effectively final
  • Method References: Shorter notation for Lambda expressions

Functional Interfaces

- **Predicate<T>**: boolean test(T t) - Testing
- **Function<T,R>**: R apply(T t) - Transformation
- **Consumer<T>**: void accept(T t) - Consumption
- **Supplier<T>**: T get() - Generation
- **UnaryOperator<T>**: T apply(T t) - Unary operation
- **BinaryOperator<T>**: T apply(T t1, T t2) - Binary operation
```java

### Stream Operations
- **Intermediate**: map, filter, sorted, distinct, limit, skip
- **Terminal**: forEach, collect, reduce, count, anyMatch, allMatch
- **Short-circuiting**: findFirst, findAny, anyMatch, allMatch, noneMatch

## Exam-Relevant Key Points

- **Stream API**: Functional data processing in Java 8+
- **Lambda Expressions**: Anonymous functions with compact syntax
- **Functional Interfaces**: Interfaces with a single abstract method
- **Map**: Transformation of elements
- **Filter**: Selection based on predicates
- **Reduce**: Aggregation of stream elements
- **Collect**: Gathering results in containers
- **IHK-relevant**: Modern Java, functional programming

## Core Components

1. **Lambda Expressions**: Compact function literals
2. **Functional Interfaces**: Typed function definitions
3. **Stream Creation**: Collections, Arrays, I/O, Generators
4. **Intermediate Operations**: Lazy, chainable operations
5. **Terminal Operations**: Eager, terminate stream processing
6. **Collectors**: Specialized collection operations
7. **Parallel Streams**: Parallel data processing
8. **Optional**: Null-safe container for values

## Practical Examples

### 1. Basic Stream Operations
```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 Basics ===");
        
        // Data source
        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);
        
        // Method References
        methodenreferenzenDemo();
    }
    
    private static void filterDemo(List<String> namen, List<Integer> zahlen) {
        System.out.println("\n--- Filter Demo ---");
        
        // Lambda expression for filter
        List<String> langeNamen = namen.stream()
            .filter(name -> name.length() > 4)
            .collect(Collectors.toList());
        
        System.out.println("Names with > 4 characters: " + langeNamen);
        
        // Multiple filters
        List<Integer> gefilterteZahlen = zahlen.stream()
            .filter(zahl -> zahl % 2 == 0)  // Even numbers
            .filter(zahl -> zahl > 3)       // Greater than 3
            .collect(Collectors.toList());
        
        System.out.println("Even numbers > 3: " + gefilterteZahlen);
        
        // Complex predicate
        Predicate<String> komplexesPraedikat = name -> 
            name.startsWith("A") && name.length() <= 5;
        
        List<String> gefilterteNamen = namen.stream()
            .filter(komplexesPraedikat)
            .collect(Collectors.toList());
        
        System.out.println("Names starting with 'A' and ≤5 characters: " + gefilterteNamen);
    }
    
    private static void mapDemo(List<String> namen, List<Integer> zahlen) {
        System.out.println("\n--- Map Demo ---");
        
        // String to Integer (length)
        List<Integer> namenslaengen = namen.stream()
            .map(name -> name.length())
            .collect(Collectors.toList());
        
        System.out.println("Name lengths: " + namenslaengen);
        
        // Integer to String (squares)
        List<String> quadrate = zahlen.stream()
            .map(zahl -> zahl * zahl)
            .map(quad -> "Square: " + quad)
            .collect(Collectors.toList());
        
        System.out.println("Squares: " + quadrate);
        
        // FlatMap for nested structures
        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("Flattened: " + flach);
        
        // Map with objects
        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("Person info: " + personenInfo);
    }
    
    private static void reduceDemo(List<Integer> zahlen) {
        System.out.println("\n--- Reduce Demo ---");
        
        // Sum with Reduce
        Optional<Integer> summe = zahlen.stream()
            .reduce((a, b) -> a + b);
        
        System.out.println("Sum: " + summe.orElse(0));
        
        // Product with Reduce
        Optional<Integer> produkt = zahlen.stream()
            .reduce((a, b) -> a * b);
        
        System.out.println("Product: " + produkt.orElse(1));
        
        // Maximum with Reduce
        Optional<Integer> maximum = zahlen.stream()
            .reduce(Integer::max);
        
        System.out.println("Maximum: " + maximum.orElse(0));
        
        // Reduce with identity value
        int summeMitIdentitaet = zahlen.stream()
            .reduce(0, Integer::sum);
        
        System.out.println("Sum with identity: " + summeMitIdentitaet);
        
        // String concatenation
        List<String> woerter = Arrays.asList("Java", "Stream", "API");
        Optional<String> verkettet = woerter.stream()
            .reduce((a, b) -> a + " " + b);
        
        System.out.println("Concatenated: " + 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("Uppercase: " + grossgeschrieben);
        
        // To Set
        Set<Integer> quadrate = zahlen.stream()
            .map(zahl -> zahl * zahl)
            .collect(Collectors.toSet());
        
        System.out.println("Squares as 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("Names map: " + namenMap);
        
        // Grouping By
        Map<Integer, List<String>> nachLaengeGruppiert = namen.stream()
            .collect(Collectors.groupingBy(String::length));
        
        System.out.println("Grouped by length: " + nachLaengeGruppiert);
        
        // Partitioning By
        Map<Boolean, List<Integer>> geradeUngerade = zahlen.stream()
            .collect(Collectors.partitioningBy(zahl -> zahl % 2 == 0));
        
        System.out.println("Partitioned: " + geradeUngerade);
        
        // Joining
        String namensliste = namen.stream()
            .collect(Collectors.joining(", ", "[", "]"));
        
        System.out.println("Names list: " + namensliste);
        
        // Summarizing
        IntSummaryStatistics statistik = zahlen.stream()
            .collect(Collectors.summarizingInt(Integer::intValue));
        
        System.out.println("Statistics: " + statistik);
    }
    
    private static void methodenreferenzenDemo() {
        System.out.println("\n--- Method References Demo ---");
        
        List<String> namen = Arrays.asList("alice", "bob", "charlie");
        
        // Static method reference
        List<String> grossgeschrieben = namen.stream()
            .map(String::toUpperCase)
            .collect(Collectors.toList());
        
        System.out.println("Static reference: " + grossgeschrieben);
        
        // Instance method reference
        List<Integer> laengen = namen.stream()
            .map(String::length)
            .collect(Collectors.toList());
        
        System.out.println("Instance reference: " + laengen);
        
        // Constructor reference
        List<Person> personen = namen.stream()
            .map(name -> new Person(name, 20 + name.length()))
            .collect(Collectors.toList());
        
        System.out.println("Constructor reference: " + 
                          personen.stream()
                                 .map(Person::getName)
                                 .collect(Collectors.toList()));
    }
    
    // Helper class
    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. Advanced Stream Operations

import java.util.*;
import java.util.stream.*;
import java.util.function.*;

public class FortgeschritteneStreams {
    
    public static void main(String[] args) {
        System.out.println("=== Advanced Stream Operations ===");
        
        // Data for demonstrations
        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)
        );
        
        // Sorting
        sortierungDemo(studenten);
        
        // Limit and Skip
        limitSkipDemo(studenten);
        
        // Distinct
        distinctDemo();
        
        // Match operations
        matchDemo(studenten);
        
        // Find operations
        findDemo(studenten);
        
        // Optional handling
        optionalDemo(studenten);
        
        // Parallel Streams
        parallelStreamDemo(studenten);
    }
    
    private static void sortierungDemo(List<Student> studenten) {
        System.out.println("\n--- Sorting Demo ---");
        
        // Sort by grade
        List<Student> nachNote = studenten.stream()
            .sorted(Comparator.comparing(Student::getNote))
            .collect(Collectors.toList());
        
        System.out.println("By grade ascending:");
        nachNote.forEach(s -> System.out.println("  " + s.getName() + ": " + s.getNote()));
        
        // Sort by grade descending
        List<Student> nachNoteAbsteigend = studenten.stream()
            .sorted(Comparator.comparing(Student::getNote).reversed())
            .collect(Collectors.toList());
        
        System.out.println("\nBy grade descending:");
        nachNoteAbsteigend.forEach(s -> System.out.println("  " + s.getName() + ": " + s.getNote()));
        
        // Multi-criteria sorting
        List<Student> mehrkriterium = studenten.stream()
            .sorted(Comparator
                .comparing(Student::getFach)
                .thenComparing(Student::getNote)
                .thenComparing(Student::getName))
            .collect(Collectors.toList());
        
        System.out.println("\nBy subject, grade, 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 and Skip Demo ---");
        
        // First 3 students
        List<Student> ersteDrei = studenten.stream()
            .limit(3)
            .collect(Collectors.toList());
        
        System.out.println("First 3 students:");
        ersteDrei.forEach(s -> System.out.println("  " + s.getName()));
        
        // Skip the first 2
        List<Student> nachUeberspringen = studenten.stream()
            .skip(2)
            .collect(Collectors.toList());
        
        System.out.println("\nAfter skipping the first 2:");
        nachUeberspringen.forEach(s -> System.out.println("  " + s.getName()));
        
        // Pagination (Page 2, 2 elements per page)
        int seite = 2;
        int groesse = 2;
        List<Student> paginiert = studenten.stream()
            .skip((seite - 1) * groesse)
            .limit(groesse)
            .collect(Collectors.toList());
        
        System.out.println("\nPage " + seite + " (size " + 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("With duplicates: " + zahlenMitDuplikaten);
        System.out.println("Unique: " + eindeutigeZahlen);
        
        // Distinct with objects
        List<String> faecher = Arrays.asList("Informatik", "Mathematik", "Informatik", 
                                           "Physik", "Mathematik", "Informatik");
        
        List<String> eindeutigeFaecher = faecher.stream()
            .distinct()
            .collect(Collectors.toList());
        
        System.out.println("\nSubjects with duplicates: " + faecher);
        System.out.println("Unique subjects: " + eindeutigeFaecher);
    }
    
    private static void matchDemo(List<Student> studenten) {
        System.out.println("\n--- Match Demo ---");
        
        // All Match - all meet condition
        boolean alleBestanden = studenten.stream()
            .allMatch(s -> s.getNote() >= 50);
        
        System.out.println("All passed: " + alleBestanden);
        
        boolean alleInformatik = studenten.stream()
            .allMatch(s -> s.getFach().equals("Informatik"));
        
        System.out.println("All Computer Science: " + alleInformatik);
        
        // Any Match - at least one meets condition
        boolean jemandInformatik = studenten.stream()
            .anyMatch(s -> s.getFach().equals("Informatik"));
        
        System.out.println("Someone Computer Science: " + jemandInformatik);
        
        boolean jemandSehrGut = studenten.stream()
            .anyMatch(s -> s.getNote() >= 90);
        
        System.out.println("Someone excellent: " + jemandSehrGut);
        
        // None Match - none meet condition
        boolean niemandDurchgefallen = studenten.stream()
            .noneMatch(s -> s.getNote() < 50);
        
        System.out.println("No one failed: " + niemandDurchgefallen);
    }
    
    private static void findDemo(List<Student> studenten) {
        System.out.println("\n--- Find Demo ---");
        
        // Find First - first element
        Optional<Student> erster = studenten.stream()
            .findFirst();
        
        erster.ifPresent(s -> System.out.println("First student: " + s.getName()));
        
        // Find Any - any element (especially with parallel streams)
        Optional<Student> irgendeinInformatiker = studenten.stream()
            .filter(s -> s.getFach().equals("Informatik"))
            .findAny();
        
        irgendeinInformatiker.ifPresent(s -> 
            System.out.println("Any computer scientist: " + s.getName()));
        
        // Find with complex predicate
        Optional<Student> besterMathematiker = studenten.stream()
            .filter(s -> s.getFach().equals("Mathematik"))
            .max(Comparator.comparing(Student::getNote));
        
        besterMathematiker.ifPresent(s -> 
            System.out.println("Best mathematician: " + s.getName() + " (" + s.getNote() + ")"));
    }
    
    private static void optionalDemo(List<Student> studenten) {
        System.out.println("\n--- Optional Handling Demo ---");
        
        // Optional with map
        Optional<String> ersterName = studenten.stream()
            .findFirst()
            .map(Student::getName);
        
        ersterName.ifPresent(name -> System.out.println("First name: " + name));
        
        // Optional with filter
        Optional<Student> besterStudent = studenten.stream()
            .max(Comparator.comparing(Student::getNote));
        
        String besterName = besterStudent
            .filter(s -> s.getNote() >= 90)
            .map(Student::getName)
            .orElse("None with 90+ points");
        
        System.out.println("Best 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("Subject of the best: " + fach));
        
        // Optional with supplier
        String defaultValue = studenten.stream()
            .filter(s -> s.getName().equals("NichtExistent"))
            .findFirst()
            .map(Student::getName)
            .orElseGet(() -> "Default-Student");
        
        System.out.println("Default value: " + defaultValue);
    }
    
    private static void parallelStreamDemo(List<Student> studenten) {
        System.out.println("\n--- Parallel Stream Demo ---");
        
        // Parallel processing
        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 result: " + namenParallel);
        System.out.println("Parallel time: " + (endZeit - startZeit) + "ms");
        
        // Comparison with sequential processing
        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("\nSequential result: " + namenSequentiell);
        System.out.println("Sequential time: " + (endZeit - startZeit) + "ms");
        
        // Thread info in parallel stream
        System.out.println("\nThread info in parallel stream:");
        studenten.parallelStream()
            .forEach(s -> System.out.println(s.getName() + " on " + 
                                             Thread.currentThread().getName()));
    }
    
    // Student class
    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. Specialized Collectors and 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 ---");
        
        // Grouping by category with statistics
        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("  Average: " + statistik.getAverage());
            System.out.println("  Minimum: " + statistik.getMin());
            System.out.println("  Maximum: " + statistik.getMax());
            System.out.println("  Sum: " + statistik.getSum());
        });
        
        // Grouping with mapping
        Map<String, Set<String>> kategorieNamen = produkte.stream()
            .collect(Collectors.groupingBy(
                Produkt::getKategorie,
                Collectors.mapping(Produkt::getName, Collectors.toSet())
            ));
        
        System.out.println("\nCategories with product names:");
        kategorieNamen.forEach((kategorie, namen) -> 
            System.out.println(kategorie + ": " + namen));
        
        // Grouping with filter
        Map<String, List<Produkt>> teureProdukte = produkte.stream()
            .collect(Collectors.groupingBy(
                Produkt::getKategorie,
                Collectors.filtering(p -> p.getPreis() > 50, Collectors.toList())
            ));
        
        System.out.println("\nExpensive products (>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 Grouping ---");
        
        // Group products by price categories
        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 grouping:");
        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 for string concatenation with separator and prefix/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 result: " + ergebnis);
        
        // Custom collector for statistics
        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 with counting
        Map<String, Long> anzahlProKategorie = produkte.stream()
            .collect(Collectors.groupingBy(
                Produkt::getKategorie,
                Collectors.counting()
            ));
        
        System.out.println("Count per category:");
        anzahlProKategorie.forEach((kategorie, anzahl) -> 
            System.out.println(kategorie + ": " + anzahl));
        
        // GroupingBy with summing
        Map<String, Integer> lagerProKategorie = produkte.stream()
            .collect(Collectors.groupingBy(
                Produkt::getKategorie,
                Collectors.summingInt(Produkt::getLagerbestand)
            ));
        
        System.out.println("\nInventory per category:");
        lagerProKategorie.forEach((kategorie, bestand) -> 
            System.out.println(kategorie + ": " + bestand));
        
        // GroupingBy with maxBy
        Map<String, Optional<Produkt>> teuerstesProKategorie = produkte.stream()
            .collect(Collectors.groupingBy(
                Produkt::getKategorie,
                Collectors.maxBy(Comparator.comparing(Produkt::getPreis))
            ));
        
        System.out.println("\nMost expensive product per category:");
        teuerstesProKategorie.forEach((kategorie, optional) -> 
            optional.ifPresent(p -> System.out.println(kategorie + ": " + p.getName())));
        
        // CollectingAndThen for post-processed results
        Map<String, String> kategorieInfo = produkte.stream()
            .collect(Collectors.groupingBy(
                Produkt::getKategorie,
                Collectors.collectingAndThen(
                    Collectors.toList(),
                    liste -> liste.size() + " products, " +
                             String.format("%.2f€", 
                                 liste.stream().mapToDouble(Produkt::getPreis).average().orElse(0))
                )
            ));
        
        System.out.println("\nCategory 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("Sum 1-9: " + summe);
        
        // With boxed to object stream
        List<Integer> zahlenListe = IntStream.rangeClosed(1, 5)
            .boxed()
            .collect(Collectors.toList());
        
        System.out.println("Numbers as list: " + zahlenListe);
        
        // DoubleStream with calculations
        double[] preise = {19.99, 29.99, 99.99, 149.99};
        
        DoubleSummaryStatistics preisStatistik = Arrays.stream(preise)
            .summaryStatistics();
        
        System.out.println("\nPrice statistics:");
        System.out.println("  Count: " + preisStatistik.getCount());
        System.out.println("  Sum: " + preisStatistik.getSum());
        System.out.println("  Average: " + preisStatistik.getAverage());
        System.out.println("  Min: " + preisStatistik.getMin());
        System.out.println("  Max: " + preisStatistik.getMax());
        
        // LongStream for large numbers
        long fakultaet = LongStream.rangeClosed(1, 10)
            .reduce(1, (a, b) -> a * b);
        
        System.out.println("\n10! = " + fakultaet);
        
        // Primitive stream with filter
        long geradeZahlen = IntStream.rangeClosed(1, 20)
            .filter(n -> n % 2 == 0)
            .count();
        
        System.out.println("Even numbers 1-20: " + geradeZahlen);
        
        // MapToObj for transformation
        List<String> zahlenAlsStrings = IntStream.rangeClosed(1, 5)
            .mapToObj(n -> "Number " + n)
            .collect(Collectors.toList());
        
        System.out.println("Numbers as strings: " + zahlenAlsStrings);
    }
    
    // Product class
    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 Overview

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 Operations Overview

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

Method Reference Types

Static Method Reference

// Lambda: s -> Integer.parseInt(s)
// Method reference: Integer::parseInt
list.stream().map(Integer::parseInt)

Instance Method Reference

// Lambda: s -> s.toUpperCase()
// Method reference: String::toUpperCase
list.stream().map(String::toUpperCase)

Constructor Reference

// Lambda: name -> new Person(name)
// Method reference: Person::new
list.stream().map(Person::new)

Performance Considerations

When to Use Streams

  • Complex Data Processing: Filter, map, reduce operations
  • Readability: Declarative code instead of imperative loops
  • Parallelization: Simple switch to parallel processing
  • Functional Programming: Immutable data structures

When to Avoid Streams

  • Simple Operations: Traditional loops are often faster
  • Performance-Critical Code: Stream overhead can be relevant
  • Primitive Arrays: Specialized operations often better
  • Very Small Collections: Overhead outweighs benefits

Advantages and Disadvantages

Advantages of Stream API

  • Readability: Declarative, expressive syntax
  • Composition: Easily chainable operations
  • Parallelization: Simple switch to parallel processing
  • Functional: Support for functional programming
  • Lazy Evaluation: Efficient processing

Disadvantages

  • Performance: Overhead with simple operations
  • Debugging: More difficult than imperative loops
  • Learning Curve: New concepts and syntax
  • Memory: Intermediate collections can consume memory

Common Exam Questions

  1. What is the difference between intermediate and terminal operations? Intermediate operations are lazy and return a Stream, terminal operations are eager and end the processing.

  2. Explain lambda expressions! Anonymous functions with compact syntax: (parameter) -> expression or (parameter) -> { statements }.

  3. When do you use method references? As a shorter alternative to lambda expressions when a method exists that matches exactly.

  4. What is the advantage of parallel streams? Automatic parallel processing on multi-core systems for better performance.

Most Important Sources

  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
Back to Blog
Share:

Related Posts