Implementing Functional Reduction: The reduce() Method – Streams
By Stephen Trude / December 23, 2022 / No Comments / Certifications of Oracle, Consumer Action on Stream Elements, Numeric Optional Classes
Implementing Functional Reduction: The reduce() Method
A functional reduction combines all elements in a stream to produce a single immutable value as its result. The reduction process employs an accumulator that repeatedly computes a new partial result based on the current partial result and the current element in the stream. The stream thus gets shorter by one element. When all elements have been combined, the last partial result that was computed by the accumulator is returned as the final result of the reduction process.
The following terminal operations are special cases of functional reduction:
The overloaded reduce() method can be used to implement new forms of functional reduction.
Optional<T> reduce(BinaryOperator<T> accumulator)
This terminal operation returns an Optional with the cumulative result of applying the accumulator on the elements of this stream: e1 ⊕ e2 ⊕ e3 …, where each ei is an element of this stream and ⊕ is the accumulator. If the stream is empty, an empty Optional is returned.
The accumulator must be associative—that is, the result of evaluating an expression is the same, regardless of how the operands are grouped to evaluate the expression. For example, the grouping in the expression below allows the subexpressions to be evaluated in parallel and their results combined by the accumulator:
e
i
⊕ e
j
⊕ e
k
⊕ e
l
== (e
i
⊕ e
j
) ⊕ (e
k
⊕ e
l
)
where ei, ej, ek, and el are operands, and ⊕ is the accumulator. For example, numeric addition, min, max, and string concatenation are associative operations, whereas subtraction and division are nonassociative.
The accumulator must also be a non-interfering and stateless function (p. 909).
Note that the method reduces a Stream of type T to a result that is an Optional of type T.
A counterpart to the single-argument reduce() method is also provided for the numeric streams.
T reduce(T identity, BinaryOperator<T> accumulator)
This terminal operation returns the cumulative result of applying the accumulator on the elements of this stream: identity ⊕ e1 ⊕ e2 ⊕ e3 …, where ei is an element of this stream, and ⊕ is the accumulator. The identity value is the initial value to accumulate. If the stream is empty, the identity value is returned.
The identity value must be an identity for the accumulator—for all ei, identity ⊕ ei == ei. The accumulator must be associative.
The accumulator must also be a non-interfering and stateless function (p. 909).
Note that the method reduces a Stream of type T to a result of type T.
A counterpart to the two-argument reduce() method is also provided for the numeric streams.
<U> U reduce(
U identity,
BiFunction<U,? super T,U> accumulator,
BinaryOperator<U> combiner)
This terminal operation returns the cumulative result of applying the accumulator on the elements of this stream, using the identity value of type U as the initial value to accumulate. If the stream is empty, the identity value is returned.
The identity value must be an identity for the combiner function. The accumulator and the combiner function must also satisfy the following relationship for all u and t of type U and T, respectively:
u © (identity ⊕ t) == u ⊕ t
where © and ⊕ are the accumulator and combiner functions, respectively.
The combiner function combines two values during stream processing. It may not be executed for a sequential stream, but for a parallel stream, it will combine cumulative results of segments that are processed concurrently.
Both the accumulator and the combiner must also be non-interfering and stateless functions (p. 909).
Note that the accumulator has the function type (U, T) -> U, and the combiner function has the function type (U, U) -> U, where the type parameters U and T are always the types of the partial result and the stream element, respectively. This method reduces a Stream of type T to a result of type U.
There is no counterpart to the three-argument reduce() method for the numeric streams.
The idiom of using a loop for calculating the sum of a finite number of values is something that is ingrained into all aspiring programmers. A loop-based solution to calculate the total number of tracks on CDs in a list is shown below, where the variable sum will hold the result after the execution of the for(:) loop:
int sum = 0; // (1) Initialize the partial result.
for (CD cd : CD.cdList) { // (2) Iterate over the list.
int numOfTracks = cd.noOfTracks(); // (3) Get the current value.
sum = sum + numOfTracks; // (4) Calculate new partial result.
}