Java Stream API

The Java Stream API is a powerful tool for processing data in Java. It allows you to declaratively specify how data should be processed, making it easier to read, write, and understand.

  • A Stream represents a sequence of elements supporting sequential and parallel aggregate operations. collapsed:: true
    • Sequential logseq.order-list-type:: number id:: 66e00de8-0740-4720-82e4-ac655e3953f5
      • processing happens in a single thread, so each element is handled in sequence.
      • List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        numbers.stream()
               .map(n -> n * 2)   // Processes each element sequentially
               .forEach(System.out::println);
    • Parallel logseq.order-list-type:: number
      • elements are processed concurrently using multiple threads. The stream is divided into chunks, and each chunk is processed simultaneously in different threads.
      • List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        numbers.parallelStream()
               .map(n -> n * 2)   // Processes elements in parallel
               .forEach(System.out::println);
      • Here, the numbers may be processed in any order, as they are handled by different threads simultaneously.
  • Streams don’t store data, they operate on the source (like a collection).
  • Streams are lazily evaluated (operations are performed only when required).
  • creating streams

    • can be created from collections, arrays, or by generating data. collapsed:: true
      • collections logseq.order-list-type:: number
        • List<String> list = Arrays.asList("a", "b", "c");
          Stream<String> stream = list.stream();
      • arrays logseq.order-list-type:: number
        • int[] arr = {1, 2, 3, 4};
          IntStream stream = Arrays.stream(arr);
      • Stream.of() logseq.order-list-type:: number
        • Stream<String> stream = Stream.of("a", "b", "c");
      • Infinite streams logseq.order-list-type:: number
        • Stream<Integer> infinite = Stream.iterate(0, n -> n + 1);
  • stream operations

    collapsed:: true
    • Intermediate operations logseq.order-list-type:: number collapsed:: true
      • return a new stream
      • lazy (executed only when a terminal operation is called)
      • Examples
        • ((66e04318-15f8-4319-ac29-1b059b24296f))
        • ((66e043a1-8ef5-4ec7-829d-e16926a9fc2f))
        • ((66e04327-51b8-4e2b-a431-4f3098b3f888))
        • ((66e04415-d131-424b-be0b-ceb015f76225))
        • ((66e0441b-49b0-4830-8874-da5498cf3063))
        • ((66e0442a-679d-42ff-8ef6-8ca8deba0a0b))
    • Terminal operations logseq.order-list-type:: number collapsed:: true
      • triggers the execution of stream operations & produces a result
      • Examples
        • collect
        • forEach
        • reduce
        • count
        • findFirst
        • findAny
        • allMatch
        • anyMatch
        • noneMatch()
    • filter

      id:: 66e04318-15f8-4319-ac29-1b059b24296f
      • Filters elements based on a predicate.
      • List<Integer> even = numbers.stream()
                                    .filter(n -> n % 2 == 0)
                                    .collect(Collectors.toList());
    • map

      id:: 66e043a1-8ef5-4ec7-829d-e16926a9fc2f
      • Transforms elements
    • flatMap

      id:: 66e03e87-f326-4c66-9796-ef23826b8b62 collapsed:: true
      • flatMap() flattens nested structures (e.g., streams of lists).
      • flatMap() takes a function that returns a stream for each element. It then concatenates these streams into a single stream.
      • Examples:
        • List<List<String>> namesList = Arrays.asList(Arrays.asList("John", "Jane"), Arrays.asList("Mike", "Kelly"));
          List<String> flatNames = namesList.stream()
                                            .flatMap(Collection::stream)
                                            .collect(Collectors.toList());
          • the function List::stream converts each list into a stream, and flatMap() combines all these streams into one.
          • $$[ [John, Jane], [Mike, Kelly] ] -> Stream[John, Jane, Mike, Kelly] $$
        • List<String> sentences = Arrays.asList(
                      "Hello world",
                      "Java Stream API",
                      "flatMap example"
                  );
           
                  // Use flatMap with a custom function to split sentences into words
                  List<String> words = sentences.stream()
                                                .flatMap(sentence -> Arrays.stream(sentence.split(" ")))  // Custom function
                                                .collect(Collectors.toList());
          • $$[Hello, world, Java, Stream, API, flatMap, example]$$
    • distinct

      id:: 66e04327-51b8-4e2b-a431-4f3098b3f888 collapsed:: true
      • Removes duplicate elements from the stream based on the equals() method.
      • List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
        List<Integer> distinctNumbers = numbers.stream()
                                               .distinct()  // Removes duplicates
                                               .collect(Collectors.toList());
         
        System.out.println(distinctNumbers);  // Output: [1, 2, 3, 4, 5]
    • sorted

      id:: 66e04415-d131-424b-be0b-ceb015f76225
    • limit

      id:: 66e0441b-49b0-4830-8874-da5498cf3063
      • List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        List<Integer> firstTwo = numbers.stream()
                                        .limit(2)  // Keep only the first 2 elements
                                        .collect(Collectors.toList());
         
        System.out.println(firstTwo);  // Output: [1, 2]
    • skip

      id:: 66e0442a-679d-42ff-8ef6-8ca8deba0a0b
      • List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        List<Integer> remaining = numbers.stream()
                                         .skip(2)  // Skip the first 2 elements
                                         .collect(Collectors.toList());
         
        System.out.println(remaining);  // Output: [3, 4, 5]
    • reduce

      • Example: sum of numbers
        • List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
          int sum = numbers.stream()
                           .reduce(0, Integer::sum);  // Initial value is 0
           
          System.out.println(sum);  // Output: 15
      • Example: concatenate strings
        • List<String> words = Arrays.asList("Java", "Stream", "API");
          String concatenated = words.stream()
                                     .reduce("", (a, b) -> a + b + " ");  // Concatenate with space
           
          System.out.println(concatenated.trim());  // Output: Java Stream API
    • peek
      • Allows you to perform a side-effect operation (such as logging) on each element as it passes through the stream.
      • mainly for debugging.
      • List<String> words = Arrays.asList("one", "two", "three");
        List<String> result = words.stream()
                                   .peek(word -> System.out.println("Processing: " + word))  // Side effect
                                   .collect(Collectors.toList());
         
        System.out.println(result);  // Output: [one, two, three]
    • collect

      • Collects the result of the stream into a collection (like a List, Set, or Map) or even performs more advanced operations like summarizing.
      • Example (collecting to a List):
        • List<String> words = Arrays.asList("Java", "Stream", "API");
          List<String> result = words.stream()
                                     .collect(Collectors.toList());
           
          System.out.println(result);  // Output: [Java, Stream, API]
      • Example (grouping by length):
        • List<String> words = Arrays.asList("one", "two", "three", "four");
          Map<Integer, List<String>> groupedByLength = 
            	words.stream()
            		.collect(Collectors.groupingBy(String::length));
           
          System.out.println(groupedByLength);  // Output: {3=[one, two], 4=[four], 5=[three]}
    • forEach()
    • reduce()
      • Reduces the stream to a single value.
      • int sum = numbers.stream()
                         .reduce(0, Integer::sum);
    • count()
      • Returns the number of elements in the stream.
      • long count = numbers.stream().count();
    • findFirst(), findAny()
      • Find the first or any element.
    • allMatch(), anyMatch(), noneMatch() collapsed:: true
      • short-circuiting operations that check if all, any, or none of the elements in the stream match a given predicate.
      • boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0);
    • toArray
      • List<String> words = Arrays.asList("one", "two", "three");
        String[] array = words.stream()
                              .toArray(String[]::new);
         
        System.out.println(Arrays.toString(array));  // Output: [one, two, three]
  • #Interview Topics

    • Difference between map() and flatMap() id:: 66e0158f-f2b5-4243-930f-27d6c8f92949 collapsed:: true
      • map() transforms each element in the stream into another form (e.g., applying a function to each element).
      • flatMap() flattens nested structures into a single-level stream and then not only transforms each element into another form
    • Lazy evaluation ? collapsed:: true
      • Intermediate operations (like map() and filter()) don’t execute until a terminal operation (like collect()) is called.
    • Stream vs. Collection? collapsed:: true
      • Streams don’t store data, whereas collections store data. Streams are consumed once, collections can be reused.
    • How does the filter() method work internally, and when is it best used? collapsed:: true
      • The filter() method takes a predicate and processes each element in the stream. If the predicate evaluates to true for an element, that element is included in the resulting stream.
      • Internally, filter() is a lazy intermediate operation, meaning it doesn’t perform the operation until a terminal operation is invoked.
      • It’s best used when you want to filter out elements based on a specific condition, like retaining only even numbers from a list or filtering out null values.
    • How does reduce() work, and what are some common use cases? collapsed:: true
      • reduce() performs a reduction on the elements of the stream, using an [[associative]] accumulation function and optionally an identity value.
        • without an identity, it returns an Optional
      • allows you to produce a single result from the stream, such as
        • summing numbers,
        • concatenating strings,
        • or finding the minimum or maximum element.
      • Example (Sum of numbers):
        • List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
          int sum = numbers.stream()
                           .reduce(0, Integer::sum);  // Identity value is 0
           
          System.out.println(sum);  // Output: 10
      • Example (Finding maximum value):
        • List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
          Optional<Integer> max = numbers.stream()
                                         .reduce(Integer::max);
           
          max.ifPresent(System.out::println);  // Output: 4
    • What is the difference between Collection.stream() and Collection.parallelStream()?
      • Collection.stream(): Creates a sequential stream, meaning elements are processed one by one in a single thread.
      • Collection.parallelStream(): Creates a parallel stream that splits the data into multiple chunks and processes them in parallel using the [[ForkJoinPool]] , with the goal of improving performance on large datasets.
      • Parallel streams can improve performance in certain scenarios, but not all; they introduce overhead for managing concurrency and are best suited for [[CPU-bound tasks]].
    • Calculate the sum of employees’ salaries and find the max salaries per department #RealInterview
      • logseq.order-list-type:: number .mapToDouble(Employee::getSalary)
        import java.util.List;
         
        class Employee {
            // ...
            public double getSalary() {
                return salary;
            }
           	public String getDepartment() {
                return department;
            }
        }
         
        // main
        List<Employee> employees = List.of(
            new Employee("Alice", 60000, "HR"),
            new Employee("Bob", 70000, "IT"),
            new Employee("Charlie", 50000, "Finance")
        );
         
        // method 1
        double totalSalary = employees
        	.stream()
          	.mapToDouble(Employee::getSalary)
        	.sum();
         
        // method 2
        double totalSalary = employees.stream()
            .map(Employee::getSalary)
            .reduce(0.0, Double::sum);
         
        // method 3
        double totalSalary = employees.stream()
            .collect(Collectors.summingDouble(Employee::getSalary));
      • logseq.order-list-type:: number
                
        // Finding max salary per department
         
        //method 1
        Map<String, Employee> maxSalariesByDept = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment, 
                Collectors.collectingAndThen(
                    Collectors.maxBy(Comparator.comparingDouble(Employee::getSalary)),
                    max -> max.orElse(null)
                )
            ));
        // method 2
        Map<String, Employee> maxSalariesByDept = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                Collectors.reducing((e1, e2) -> e1.getSalary() > e2.getSalary() ? e1 : e2)
            )).entrySet().stream()
            .filter(e -> e.getValue().isPresent())
            .collect(Collectors.toMap(
                Map.Entry::getKey, 
                e -> e.getValue().get()
            ));
         
        // method 3
        Map<String, Employee> maxSalariesByDept = employees.stream()
            .collect(Collectors.toMap(
                Employee::getDepartment,
                e -> e,
                (e1, e2) -> e1.getSalary() > e2.getSalary() ? e1 : e2
            ));
         
        // method 4
        Map<String, Optional<Employee>> maxSalariesByDept = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                Collectors.maxBy(Comparator.comparingDouble(Employee::getSalary))
            ));
        • Collectors.toMap
          • Collectors.toMap(keyMapper, valueMapper, mergeFunction)
            • keyMapper: Function to extract or generate the key.
            • valueMapper: Function to extract or generate the value.
            • mergeFunction: Function that specifies what to do when duplicate keys are encountered.

On this page