Skip to content
IRC-CodingIRC-Coding
Java Stream APILambda ExpressionsFunctional InterfacesMap Filter ReduceCollect

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

Master Java Stream API with lambda expressions and functional interfaces. Learn map, filter, reduce, and collect for functional data processing.

S

schutzgeist

14 min read
Java Stream API: Lambda, Map, Filter, Reduce & Collect

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

This guide covers the Java Stream API comprehensively—including lambda expressions, functional interfaces, map, filter, reduce, and collect operations with practical examples.

In a Nutshell

Java Stream API enables functional data processing using lambda expressions. map transforms elements, filter selects them based on conditions, reduce aggregates values, and collect gathers results into containers.

Quick Technical Summary

Java Stream API is a functional API for processing data collections. It supports declarative programming with lambda expressions and functional interfaces.

Key concepts:

Lambda Expressions

  • Syntax: (parameter) -> expression or (parameter) -> { statements }
  • Type inference: The 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) - Test a condition
- **Function<T,R>**: R apply(T t) - Transform a value
- **Consumer<T>**: void accept(T t) - Consume a value
- **Supplier<T>**: T get() - Supply a value
- **UnaryOperator<T>**: T apply(T t) - Unary operation
- **BinaryOperator<T>**: T apply(T t1, T t2) - Binary operation

Stream Operations

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

Exam Focus 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: Transforms elements in a stream
  • Filter: Selects elements based on predicates
  • Reduce: Aggregates stream elements into a single value
  • Collect: Gathers results into containers
  • Professional certifications: Modern Java, functional programming

Core Components

  1. Lambda expressions: Compact function literals
  2. Functional interfaces: Typed function definitions
  3. Stream creation: From collections, arrays, I/O, generators
  4. Intermediate operations: Lazy, chainable transformations
  5. Terminal operations: Eager, finalize stream processing
  6. Collectors: Specialized collection operations
  7. Parallel streams: Parallel data processing
  8. Optional: Null-safe containers for values

Practical Examples

1. Basic Stream Operations

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 ===");
        
        // 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 filtering
        List<String> langeNamen = namen.stream()
            .filter(name -> name.length() > 4)
            .collect(Collectors.toList());
        
        System.out.println("Namen mit > 4 Buchstaben: " + 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("Gerade Zahlen > 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("Namen mit 'A' und ≤5 Buchstaben: " + 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("Namenslängen: " + namenslaengen);
        
        // Integer to String (squares)
        List<String> quadrate = zahlen.stream()
            .map(zahl -> zahl * zahl)
            .map(quad -> "Quadrat: " + quad)
            .collect(Collectors.toList());
        
        System.out.println("Quadrate: " + 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("Flachgemacht: " + 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("Personen-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("Summe: " + summe.orElse(0));
        
        // Product with reduce
        Optional<Integer> produkt = zahlen.stream()
            .reduce((a, b) -> a * b);
        
        System.out.println("Produkt: " + 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("Summe mit Identität: " + summeMitIdentitaet);
        
        // String concatenation
        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");
        
        // Static method reference
        List<String> grossgeschrieben = namen.stream()
            .map(String::toUpperCase)
            .collect(Collectors.toList());
        
        System.out.println("Statische Referenz: " + grossgeschrieben);
        
        // Instance method reference
        List<Integer> laengen = namen.stream()
            .map(String::length)
            .collect(Collectors.toList());
        
        System.out.println("Instanz-Referenz: " + laengen);
        
        // Constructor reference
        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()));
    }
    
    // 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 AdvancedStreams {
    
    public static void main(String[] args) {
        System.out.println("=== Advanced Stream Operations ===");
        
        // Sample data for demonstrations
        List<Student> students = Arrays.asList(
            new Student("Alice", "Computer Science", 85, 3),
            new Student("Bob", "Mathematics", 92, 2),
            new Student("Charlie", "Computer Science", 78, 4),
            new Student("Diana", "Physics", 88, 1),
            new Student("Eve", "Computer Science", 95, 2),
            new Student("Frank", "Mathematics", 73, 3)
        );
        
        // Sorting
        sortingDemo(students);
        
        // Limit and Skip
        limitSkipDemo(students);
        
        // Distinct
        distinctDemo();
        
        // Match operations
        matchDemo(students);
        
        // Find operations
        findDemo(students);
        
        // Optional handling
        optionalDemo(students);
        
        // Parallel Streams
        parallelStreamDemo(students);
    }
    
    private static void sortingDemo(List<Student> students) {
        System.out.println("\n--- Sorting Demo ---");
        
        // Sort by grade
        List<Student> byGrade = students.stream()
            .sorted(Comparator.comparing(Student::getGrade))
            .collect(Collectors.toList());
        
        System.out.println("By grade ascending:");
        byGrade.forEach(s -> System.out.println("  " + s.getName() + ": " + s.getGrade()));
        
        // Sort by grade descending
        List<Student> byGradeDescending = students.stream()
            .sorted(Comparator.comparing(Student::getGrade).reversed())
            .collect(Collectors.toList());
        
        System.out.println("\nBy grade descending:");
        byGradeDescending.forEach(s -> System.out.println("  " + s.getName() + ": " + s.getGrade()));
        
        // Multi-criteria sorting
        List<Student> multiCriteria = students.stream()
            .sorted(Comparator
                .comparing(Student::getSubject)
                .thenComparing(Student::getGrade)
                .thenComparing(Student::getName))
            .collect(Collectors.toList());
        
        System.out.println("\nBy subject, grade, name:");
        multiCriteria.forEach(s -> System.out.println("  " + s.getSubject() + " - " + 
                                                      s.getName() + ": " + s.getGrade()));
    }
    
    private static void limitSkipDemo(List<Student> students) {
        System.out.println("\n--- Limit and Skip Demo ---");
        
        // First 3 students
        List<Student> firstThree = students.stream()
            .limit(3)
            .collect(Collectors.toList());
        
        System.out.println("First 3 students:");
        firstThree.forEach(s -> System.out.println("  " + s.getName()));
        
        // Skip the first 2
        List<Student> afterSkip = students.stream()
            .skip(2)
            .collect(Collectors.toList());
        
        System.out.println("\nAfter skipping first 2:");
        afterSkip.forEach(s -> System.out.println("  " + s.getName()));
        
        // Pagination (page 2, 2 elements per page)
        int page = 2;
        int size = 2;
        List<Student> paginated = students.stream()
            .skip((page - 1) * size)
            .limit(size)
            .collect(Collectors.toList());
        
        System.out.println("\nPage " + page + " (size " + size + "):");
        paginated.forEach(s -> System.out.println("  " + s.getName()));
    }
    
    private static void distinctDemo() {
        System.out.println("\n--- Distinct Demo ---");
        
        List<Integer> numbersWithDuplicates = Arrays.asList(1, 2, 2, 3, 4, 4, 4, 5, 1);
        
        List<Integer> uniqueNumbers = numbersWithDuplicates.stream()
            .distinct()
            .collect(Collectors.toList());
        
        System.out.println("With duplicates: " + numbersWithDuplicates);
        System.out.println("Unique: " + uniqueNumbers);
        
        // Distinct with objects
        List<String> subjects = Arrays.asList("Computer Science", "Mathematics", "Computer Science", 
                                           "Physics", "Mathematics", "Computer Science");
        
        List<String> uniqueSubjects = subjects.stream()
            .distinct()
            .collect(Collectors.toList());
        
        System.out.println("\nSubjects with duplicates: " + subjects);
        System.out.println("Unique subjects: " + uniqueSubjects);
    }
    
    private static void matchDemo(List<Student> students) {
        System.out.println("\n--- Match Demo ---");
        
        // All Match - all satisfy condition
        boolean allPassed = students.stream()
            .allMatch(s -> s.getGrade() >= 50);
        
        System.out.println("All passed: " + allPassed);
        
        boolean allComputerScience = students.stream()
            .allMatch(s -> s.getSubject().equals("Computer Science"));
        
        System.out.println("All Computer Science: " + allComputerScience);
        
        // Any Match - at least one satisfies condition
        boolean someComputerScience = students.stream()
            .anyMatch(s -> s.getSubject().equals("Computer Science"));
        
        System.out.println("Some Computer Science: " + someComputerScience);
        
        boolean someExcellent = students.stream()
            .anyMatch(s -> s.getGrade() >= 90);
        
        System.out.println("Some excellent: " + someExcellent);
        
        // None Match - none satisfy condition
        boolean noneFlunked = students.stream()
            .noneMatch(s -> s.getGrade() < 50);
        
        System.out.println("None flunked: " + noneFlunked);
    }
    
    private static void findDemo(List<Student> students) {
        System.out.println("\n--- Find Demo ---");
        
        // Find First - first element
        Optional<Student> first = students.stream()
            .findFirst();
        
        first.ifPresent(s -> System.out.println("First student: " + s.getName()));
        
        // Find Any - any element (especially useful with parallel streams)
        Optional<Student> anyComputerScientist = students.stream()
            .filter(s -> s.getSubject().equals("Computer Science"))
            .findAny();
        
        anyComputerScientist.ifPresent(s -> 
            System.out.println("Any Computer Science student: " + s.getName()));
        
        // Find with complex predicate
        Optional<Student> bestMathematician = students.stream()
            .filter(s -> s.getSubject().equals("Mathematics"))
            .max(Comparator.comparing(Student::getGrade));
        
        bestMathematician.ifPresent(s -> 
            System.out.println("Best mathematician: " + s.getName() + " (" + s.getGrade() + ")"));
    }
    
    private static void optionalDemo(List<Student> students) {
        System.out.println("\n--- Optional Handling Demo ---");
        
        // Optional with map
        Optional<String> firstName = students.stream()
            .findFirst()
            .map(Student::getName);
        
        firstName.ifPresent(name -> System.out.println("First name: " + name));
        
        // Optional with filter
        Optional<Student> topStudent = students.stream()
            .max(Comparator.comparing(Student::getGrade));
        
        String topName = topStudent
            .filter(s -> s.getGrade() >= 90)
            .map(Student::getName)
            .orElse("None with 90+ points");
        
        System.out.println("Top student (90+): " + topName);
        
        // Optional chaining
        Optional<String> topSubject = students.stream()
            .max(Comparator.comparing(Student::getGrade))
            .flatMap(s -> Optional.ofNullable(s.getSubject()))
            .map(String::toUpperCase);
        
        topSubject.ifPresent(subject -> 
            System.out.println("Top student's subject: " + subject));
        
        // Optional with supplier
        String defaultValue = students.stream()
            .filter(s -> s.getName().equals("NonExistent"))
            .findFirst()
            .map(Student::getName)
            .orElseGet(() -> "Default Student");
        
        System.out.println("Default value: " + defaultValue);
    }
    
    private static void parallelStreamDemo(List<Student> students) {
        System.out.println("\n--- Parallel Stream Demo ---");
        
        // Parallel processing
        long startTime = System.currentTimeMillis();
        
        List<String> namesParallel = students.parallelStream()
            .filter(s -> s.getGrade() > 80)
            .map(Student::getName)
            .sorted()
            .collect(Collectors.toList());
        
        long endTime = System.currentTimeMillis();
        
        System.out.println("Parallel result: " + namesParallel);
        System.out.println("Parallel time: " + (endTime - startTime) + "ms");
        
        // Comparison with sequential processing
        startTime = System.currentTimeMillis();
        
        List<String> namesSequential = students.stream()
            .filter(s -> s.getGrade() > 80)
            .map(Student::getName)
            .sorted()
            .collect(Collectors.toList());
        
        endTime = System.currentTimeMillis();
        
        System.out.println("\nSequential result: " + namesSequential);
        System.out.println("Sequential time: " + (endTime - startTime) + "ms");
        
        // Thread info with parallel stream
        System.out.println("\nThread info with parallel stream:");
        students.parallelStream()
            .forEach(s -> System.out.println(s.getName() + " on " + 
                                             Thread.currentThread().getName()));
    }
    
    // Student class
    static class Student {
        private String name;
        private String subject;
        private int grade;
        private int semester;
        
        public Student(String name, String subject, int grade, int semester) {
            this.name = name;
            this.subject = subject;
            this.grade = grade;
            this.semester = semester;
        }
        
        public String getName() { return name; }
        public String getSubject() { return subject; }
        public int getGrade() { return grade; }
        public int getSemester() { return semester; }
    }
}

3. Specialized Collectors and Custom Operations

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

public class SpecializedCollectors {
    
    public static void main(String[] args) {
        System.out.println("=== Specialized Collectors Demo ===");
        
        // Test data
        List<Product> products = Arrays.asList(
            new Product("Laptop", "Electronics", 999.99, 5),
            new Product("Mouse", "Electronics", 29.99, 15),
            new Product("Keyboard", "Electronics", 79.99, 8),
            new Product("Book", "Books", 19.99, 20),
            new Product("Pen", "Office", 2.99, 50),
            new Product("Paper", "Office", 9.99, 30)
        );
        
        // Grouping with aggregation
        groupingWithAggregation(products);
        
        // Multi-level grouping
        multiLevelGrouping(products);
        
        // Custom collector
        customCollectorDemo();
        
        // Downstream collectors
        downstreamCollectorsDemo(products);
        
        // Primitive streams
        primitiveStreamsDemo();
    }
    
    private static void groupingWithAggregation(List<Product> products) {
        System.out.println("\n--- Grouping with Aggregation ---");
        
        // Group by category with statistics
        Map<String, DoubleSummaryStatistics> priceStatistics = products.stream()
            .collect(Collectors.groupingBy(
                Product::getCategory,
                Collectors.summarizingDouble(Product::getPrice)
            ));
        
        priceStatistics.forEach((category, statistics) -> {
            System.out.println(category + ":");
            System.out.println("  Average: " + statistics.getAverage());
            System.out.println("  Minimum: " + statistics.getMin());
            System.out.println("  Maximum: " + statistics.getMax());
            System.out.println("  Sum: " + statistics.getSum());
        });
        
        // Grouping with mapping
        Map<String, Set<String>> categoryNames = products.stream()
            .collect(Collectors.groupingBy(
                Product::getCategory,
                Collectors.mapping(Product::getName, Collectors.toSet())
            ));
        
        System.out.println("\nCategories with product names:");
        categoryNames.forEach((category, names) -> 
            System.out.println(category + ": " + names));
        
        // Grouping with filtering
        Map<String, List<Product>> expensiveProducts = products.stream()
            .collect(Collectors.groupingBy(
                Product::getCategory,
                Collectors.filtering(p -> p.getPrice() > 50, Collectors.toList())
            ));
        
        System.out.println("\nExpensive products (>50€):");
        expensiveProducts.forEach((category, productList) -> {
            if (!productList.isEmpty()) {
                System.out.println(category + ": " + 
                    productList.stream().map(Product::getName).collect(Collectors.toList()));
            }
        });
    }
    
    private static void multiLevelGrouping(List<Product> products) {
        System.out.println("\n--- Multi-Level Grouping ---");
        
        // Group products by price categories
        Map<String, Map<String, List<Product>>> multiLevel = products.stream()
            .collect(Collectors.groupingBy(
                p -> p.getPrice() < 50 ? "Affordable" : "Expensive",
                Collectors.groupingBy(Product::getCategory)
            ));
        
        System.out.println("Multi-level grouping:");
        multiLevel.forEach((priceCategory, categoryMap) -> {
            System.out.println(priceCategory + ":");
            categoryMap.forEach((category, productList) -> {
                System.out.println("  " + category + ": " + 
                    productList.stream().map(Product::getName).collect(Collectors.toList()));
            });
        });
    }
    
    private static void customCollectorDemo() {
        System.out.println("\n--- Custom Collector Demo ---");
        
        List<String> words = Arrays.asList("Java", "Stream", "API", "Functional", "Programming");
        
        // Custom collector for string concatenation with delimiter 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 result = words.stream().collect(customStringCollector);
        System.out.println("Custom collector result: " + result);
        
        // 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> numbers = Arrays.asList(10, 20, 30, 40, 50);
        double average = numbers.stream().collect(averageCollector);
        System.out.println("Custom average: " + average);
    }
    
    private static void downstreamCollectorsDemo(List<Product> products) {
        System.out.println("\n--- Downstream Collectors Demo ---");
        
        // GroupingBy with counting
        Map<String, Long> countPerCategory = products.stream()
            .collect(Collectors.groupingBy(
                Product::getCategory,
                Collectors.counting()
            ));
        
        System.out.println("Count per category:");
        countPerCategory.forEach((category, count) -> 
            System.out.println(category + ": " + count));
        
        // GroupingBy with summing
        Map<String, Integer> stockPerCategory = products.stream()
            .collect(Collectors.groupingBy(
                Product::getCategory,
                Collectors.summingInt(Product::getStock)
            ));
        
        System.out.println("\nStock per category:");
        stockPerCategory.forEach((category, stock) -> 
            System.out.println(category + ": " + stock));
        
        // GroupingBy with maxBy
        Map<String, Optional<Product>> mostExpensivePerCategory = products.stream()
            .collect(Collectors.groupingBy(
                Product::getCategory,
                Collectors.maxBy(Comparator.comparing(Product::getPrice))
            ));
        
        System.out.println("\nMost expensive product per category:");
        mostExpensivePerCategory.forEach((category, optional) -> 
            optional.ifPresent(p -> System.out.println(category + ": " + p.getName())));
        
        // CollectingAndThen for post-processing results
        Map<String, String> categoryInfo = products.stream()
            .collect(Collectors.groupingBy(
                Product::getCategory,
                Collectors.collectingAndThen(
                    Collectors.toList(),
                    list -> list.size() + " products, " +
                            String.format("%.2f€", 
                                list.stream().mapToDouble(Product::getPrice).average().orElse(0))
                )
            ));
        
        System.out.println("\nCategory info:");
        categoryInfo.forEach((category, info) -> System.out.println(category + ": " + info));
    }
    
    private static void primitiveStreamsDemo() {
        System.out.println("\n--- Primitive Streams Demo ---");
        
        // IntStream
        IntStream numbers = IntStream.range(1, 10);
        
        int sum = numbers.sum();
        System.out.println("Sum 1-9: " + sum);
        
        // Boxed to object stream
        List<Integer> numbersList = IntStream.rangeClosed(1, 5)
            .boxed()
            .collect(Collectors.toList());
        
        System.out.println("Numbers as list: " + numbersList);
        
        // DoubleStream with calculations
        double[] prices = {19.99, 29.99, 99.99, 149.99};
        
        DoubleSummaryStatistics priceStatistics = Arrays.stream(prices)
            .summaryStatistics();
        
        System.out.println("\nPrice statistics:");
        System.out.println("  Count: " + priceStatistics.getCount());
        System.out.println("  Sum: " + priceStatistics.getSum());
        System.out.println("  Average: " + priceStatistics.getAverage());
        System.out.println("  Min: " + priceStatistics.getMin());
        System.out.println("  Max: " + priceStatistics.getMax());
        
        // LongStream for large numbers
        long factorial = LongStream.rangeClosed(1, 10)
            .reduce(1, (a, b) -> a * b);
        
        System.out.println("\n10! = " + factorial);
        
        // Primitive stream with filter
        long evenNumbers = IntStream.rangeClosed(1, 20)
            .filter(n -> n % 2 == 0)
            .count();
        
        System.out.println("Even numbers 1-20: " + evenNumbers);
        
        // MapToObj for transformation
        List<String> numbersAsStrings = IntStream.rangeClosed(1, 5)
            .mapToObj(n -> "Number " + n)
            .collect(Collectors.toList());
        
        System.out.println("Numbers as strings: " + numbersAsStrings);
    }
    
    // Product class
    static class Product {
        private String name;
        private String category;
        private double price;
        private int stock;
        
        public Product(String name, String category, double price, int stock) {
            this.name = name;
            this.category = category;
            this.price = price;
            this.stock = stock;
        }
        
        public String getName() { return name; }
        public String getCategory() { return category; }
        public double getPrice() { return price; }
        public int getStock() { return stock; }
    }
}

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: Easy conversion 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 matter
  • Primitive arrays: Specialized operations often work better
  • Very small collections: Overhead outweighs benefits

Advantages and Disadvantages

Advantages of Stream API

  • Readability: Declarative, expressive syntax
  • Composability: Easy method chaining
  • Parallelization: Simple conversion to parallel processing
  • Functional: Strong support for functional programming patterns
  • Lazy evaluation: Efficient processing

Disadvantages

  • Performance: Overhead on simple operations
  • Debugging: More difficult than imperative loops
  • Learning curve: New concepts and syntax to master
  • Memory: Intermediate collections can consume memory

Common Exam Questions

  1. What’s the difference between intermediate and terminal operations? Intermediate operations are lazy and return a stream; terminal operations are eager and end the processing pipeline.

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

  3. When should you use method references? As a shorter alternative to lambdas when an existing method matches your needs exactly.

  4. What’s the benefit of parallel streams? Automatic parallel processing on multi-core systems for improved performance.

Key Resources

  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