Intermediate vs Terminal Operations in Java Stream API

1. Introduction

Java's Stream API categorises operations into intermediate and terminal operations. Intermediate operations return a new stream and are always lazy, meaning they start processing the content once a terminal operation is invoked. Terminal operations, on the other hand, are eager. Once invoked, they process the stream and produce a result or a side effect.

2. Key Points

1. Intermediate operations are lazy and can be chained to form a pipeline, which does not execute until a terminal operation is invoked.

2. Terminal operations are eager and trigger data processing, concluding the stream pipeline.

3. Intermediate operations transform a stream into another stream (e.g., filter, map).

4. Terminal operations produce a result (e.g., a list, a count, or simply void in the case of forEach).

3. Differences

Intermediate Operations Terminal Operations
Only execute processing once a terminal operation is invoked. They are lazy in nature. Trigger the processing of data that was specified by intermediate operations. They are eager in nature.
Return a new stream, allowing further operations to be chained. Typically, a non-stream result, such as a primitive value, a collection, or no value (void), is returned.
There can be as many as you want in a chain of Stream operations. Multiple intermediate operations can be linked together to form a complex expression. Marks the end of the stream operation chain. There can be only one terminal operation.
Examples include filter(), map(), limit(), and sorted(), which modify or transform the stream without producing any result. Examples include forEach(), collect(), reduce(), and findFirst(), which produce a result or side-effect from the stream processing.
It is used to transform the stream into another one, filter elements, or apply any function to modify the elements of the stream. It is used to produce a final result from stream processing, such as aggregating elements or performing an action on each element.

4. Example

import java.util.stream.Stream;

public class StreamOperationsExample {
    public static void main(String[] args) {
        // Intermediate operation (filter) followed by a terminal operation (forEach)
        Stream.of("apple", "banana", "cherry", "date")
            .filter(fruit -> fruit.startsWith("b")) // Intermediate operation
            .forEach(System.out::println);           // Terminal operation
    }
}

Output:

banana

Explanation:

1. filter is an intermediate operation that sets up a condition but does not start processing.

2. forEach is a terminal operation that starts processing filtered elements and performs an action on each one.

5. When to use?

- Use intermediate operations to set up a pipeline of operations you want to perform on the data.

- Use terminal operations when you're ready to initiate the processing and produce a result.

Comments