Terminal Stream Operations – Streams
By Stephen Trude / May 23, 2023 / No Comments / Certifications of Oracle, Collecting to an Array, Numeric Optional Classes
16.7 Terminal Stream Operations
A stream pipeline does not execute until a terminal operation is invoked on it; that is, a stream pipeline does not start to process the stream elements until a terminal operation is initiated. A terminal operation is said to be eager as it executes immediately when invoked—as opposed to an intermediate operation which is lazy. Invoking the terminal operation results in the intermediate operations of the stream pipeline to be executed. Understandably, a terminal operation is specified as the last operation in a stream pipeline, and there can only be one such operation in a stream pipeline. A terminal operation never returns a stream, which is always done by an intermediate operation. Once the terminal operation completes, the stream is consumed and cannot be reused.
Terminal operations can be broadly grouped into three groups:
- Operations with side effects
The Stream API provides two terminal operations, forEach() and forEachOrdered(), that are designed to allow side effects on stream elements (p. 948). These terminal operations do not return a value. They allow a Consumer action, specified as an argument, to be applied to every element, as they are consumed from the stream pipeline—for example, to print each element in the stream.
- Searching operations
These operations perform a search operation to determine a match or find an element as explained below.
All search operations are short-circuit operations; that is, the operation can terminate once the result is determined, whether or not all elements in the stream have been considered.
Search operations can be further classified into two subgroups:
❍ Matching operations
The three terminal operations anyMatch(), allMatch(), and noneMatch() determine whether stream elements match a given Predicate specified as an argument to the method (p. 949). As expected, these operations return a boolean value to indicate whether the match was successful or not.
❍ Finding operations
The two terminal operations findAny() and findFirst() find any element and the first element in a stream, respectively, if such an element is available (p. 952). As the stream might be empty and such an element might not exist, these operations return an Optional.
- Reduction operations
A reduction operation computes a result from combining the stream elements by successively applying a combining function; that is, the stream elements are reduced to a result value. Examples of reductions are computing the sum or average of numeric values in a numeric stream, and accumulating stream elements into a collection.
We distinguish between two kinds of reductions:
❍ Functional reduction
A terminal operation is a functional reduction on the elements of a stream if it reduces the elements to a single immutable value which is then returned by the operation.
The overloaded reduce() method provided by the Stream API can be used to implement customized functional reductions (p. 955), whereas the terminal operations count(), min(), and max() implement specialized functional reductions (p. 953).
Functional reductions on numeric streams are discussed later in this section (p. 972).
❍ Mutable reduction
A terminal operation performs a mutable reduction on the elements of a stream if it uses a mutable container—for example, a list, a set, or a map—to accumulate values as it processes the stream elements. The operation returns the mutable container as the result of the operation.
The Stream API provides two overloaded collect() methods that perform mutable reduction (p. 964). One overloaded collect() method can be used to implement customized mutable reductions by specifying the functions (supplier, accumulator, combiner) required to perform such a reduction. A second collect() method accepts a Collector that is used to perform a mutable reduction. A collector encapsulates the functions required for performing a mutable reduction. The Stream API provides built-in collectors that allow various containers to be used for performing mutable reductions (p. 978). When a terminal operation performs a mutable reduction using a specific container, it is said to collect to this container.
The toArray() method implements a specialized mutable reduction that returns an array with the accumulated values (p. 971); that is, the method collects to an array.