Blog

Understanding Java Stream API: A Comprehensive Guide

Java Streams have revolutionized the way we handle collections and streams of data by providing a rich API for performing complex data processing operations in a functional style. Here’s a breakdown of the essential intermediate and terminal operations, complete with examples to help you master data processing in Java.

Intermediate Operations

filter(Predicate<T>)

Filters elements based on a condition.

List<String> names = Arrays.asList("Anna", "Bob", "Charlie", "Diana");
List<String> filteredNames = names.stream()
                                  .filter(name -> name.startsWith("A"))
                                  .collect(Collectors.toList());
// Output: [Anna]

map(Function<T, R>)

Transforms elements using a provided function.

List<String> names = Arrays.asList("anna", "bob", "charlie");
List<String> capitalizedNames = names.stream()
                                      .map(String::toUpperCase)
                                      .collect(Collectors.toList());
// Output: [ANNA, BOB, CHARLIE]

flatMap(Function<T, Stream<R>>)

Flattens nested Stream objects into a single Stream.

List<List<String>> listOfLists = Arrays.asList(
  Arrays.asList("a", "b"),
  Arrays.asList("c", "d")
);
List<String> flatList = listOfLists.stream()
                                   .flatMap(List::stream)
                                   .collect(Collectors.toList());
// Output: [a, b, c, d]

Head you can read more on FlatMap

https://codetechsummit.com/flatmap-java-8-streams/

distinct()

Removes duplicates.

List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
List<Integer> distinctNumbers = numbers.stream()
                                       .distinct()
                                       .collect(Collectors.toList());
// Output: [1, 2, 3, 4, 5]

sorted()

Sorts elements.

List<Integer> numbers = Arrays.asList(4, 2, 3, 1, 5);
List<Integer> sortedNumbers = numbers.stream()
                                     .sorted()
                                     .collect(Collectors.toList());
// Output: [1, 2, 3, 4, 5]

peek(Consumer<T>)

Performs an action for each element without altering the stream.

List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.stream()
       .peek(number -> System.out.println("From stream: " + number))
       .collect(Collectors.toList());
// Output: From stream: 1
//         From stream: 2
//         From stream: 3

limit(long n)

Limits the number of elements.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> limitedNumbers = numbers.stream()
                                      .limit(3)
                                      .collect(Collectors.toList());
// Output: [1, 2, 3]

skip(long n)

Skips the first n elements.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> skippedNumbers = numbers.stream()
                                      .skip(2)
                                      .collect(Collectors.toList());
// Output: [3, 4, 5]

parallel()

Processes elements in parallel.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.parallelStream()
                 .mapToInt(Integer::intValue)
                 .sum();
// Output: Sum of numbers

sequential()

Processes elements sequentially (useful after parallel()).

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
long count = numbers.parallelStream()
                    .sequential()
                    .count();
// Output: 5

unordered()

Hints that the order of elements is not significant.

Set<Integer> numbers = new HashSet<>(Arrays.asList(5, 4, 3, 2, 1));
List<Integer> unorderedList = numbers.stream()
                                     .unordered()
                                     .collect(Collectors.toList());
// Output: [Any order]

Terminal Operations

forEach(Consumer<T>)

Performs an action for each element.

List<String> names = Arrays.asList("Anna", "Bob", "Charlie");
names.forEach(name -> System.out.println(name));
// Output: Anna
//         Bob
//         Charlie

forEachOrdered(Consumer<T>)

Ensures the action is performed in the encounter order, important for parallel streams.

List<String> names = Arrays.asList("Anna", "Bob", "Charlie");
names.parallelStream()
     .forEachOrdered(System.out::println);
// Output: Anna
//         Bob
//         Charlie

toArray()

Converts the stream into an array.

List<String> names = Arrays.asList("Anna", "Bob", "Charlie");
String[] namesArray = names.stream()
                           .toArray(String[]::new);
// Output: Array of names

reduce(BinaryOperator<T>)

Performs a reduction on the elements.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
                 .reduce(0, Integer::sum);
// Output: 15

collect(Collector<T,A,R>)

Collects elements into a container.

List<String> names = Arrays.asList("Anna", "Bob", "Charlie");
String concatenatedNames = names.stream()
                                .collect(Collectors.joining(", "));
// Output: Anna, Bob, Charlie

min(Comparator<T>) and max(Comparator<T>)

Finds the minimum or maximum element.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> min = numbers.stream()
                               .min(Integer::compare);
Optional<Integer> max = numbers.stream()
                               .max(Integer::compare);
// Output: min.isPresent() ? min.get() : "No min"
//         max.isPresent() ? max.get() : "No max"

count()

Counts the elements.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
long count = numbers.stream()
                    .count();
// Output: 5

anyMatch(Predicate<T>), allMatch(Predicate<T>), noneMatch(Predicate<T>)

Evaluates elements based on a predicate.

List<String> names = Arrays.asList("Anna", "Bob", "Charlie");
boolean anyMatchResult = names.stream()
                              .anyMatch(name -> name.startsWith("A"));
boolean allMatchResult = names.stream()
                              .allMatch(name -> name.length() > 3);
boolean noneMatchResult = names.stream()
                               .noneMatch(name -> name.endsWith("z"));
// Output: anyMatchResult, allMatchResult, noneMatchResult

findFirst() and findAny()

Finds an element.

List<String> names = Arrays.asList("Anna", "Bob", "Charlie");
Optional<String> firstFound = names.stream()
                                   .findFirst();
Optional<String> anyFound = names.stream()
                                 .findAny();
// Output: firstFound.isPresent() ? firstFound.get() : "No element"
//         anyFound.isPresent() ? anyFound.get() : "No element"

Java’s Stream API is a powerful tool that enables developers to write clean, concise, and efficient data processing code. By understanding and utilizing the various intermediate and terminal operations, you can unlock the full potential of this API in your Java applications.

Avatar

Neelabh

About Author

As Neelabh Singh, I am a Senior Software Engineer with 6.6 years of experience, specializing in Java technologies, Microservices, AWS, Algorithms, and Data Structures. I am also a technology blogger and an active participant in several online coding communities.

You may also like

Blog Design Pattern

Understanding the Builder Design Pattern in Java | Creational Design Patterns | CodeTechSummit

Overview The Builder design pattern is a creational pattern used to construct a complex object step by step. It separates
Blog Tech Toolkit

Base64 Decode

Base64 encoding is a technique used to encode binary data into ASCII characters, making it easier to transmit data over