
Reduction
The reduce(T identity, BinaryOperator<T> accumulator)
method of the Stream
interface
performs a reduction operation on the stream to reduce the stream to a single value. It takes an initial value and
an accumulator that is a BinaryOperator<T>
as arguments. The first time, the accumulator receives
the initial value and the first element of the stream as arguments, and returns a value. The second time, the
accumulator receives the value returned from its previous call and the second element from the stream. This process
continues until all elements of the stream have been passed to the accumulator.
The returned value from the last call of the accumulator is returned from the reduce(...)
method. The
following snippet of code performs the summation of all integers in the stream:
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8); Integer sum = numbers.stream().reduce(0, (n1, n2) > n1 + n2); // must be 36 System.out.print("Sum of all integers in the stream : " + sum);
The Integer
class contains a static sum(...)
method to perform sum of two integers. You can
rewrite the code using a method reference, like so:
Integer sum = numbers.stream().reduce(0, Integer::sum);
Reduction operations
A reduction operation (also called a fold) takes
a sequence of input elements and combines them into a single summary result by repeated application of a combining
operation, such as finding the sum or maximum of a set of numbers, or accumulating elements into a list. The
streams classes have multiple forms of general reduction operations, called reduce()
and collect()
,
as well as multiple specialized reduction forms such as sum()
, max()
, or count()
.
Of course, such operations can be readily implemented as simple sequential loops, as in:
int sum = 0; for (int x : numbers) { sum += x; }
However, there are good reasons to prefer a reduce operation over a mutative accumulation such as the above. Not only is a reduction "more abstract"  it operates on the stream as a whole rather than individual elements  but a properly constructed reduce operation is inherently parallelizable, so long as the function(s) used to process the elements are associative and stateless. For example, given a stream of numbers for which we want to find the sum, we can write:
int sum = numbers.stream().reduce(0, (x, y) > x + y);
or:
int sum = numbers.stream().reduce(0, Integer::sum);
These reduction operations can run safely in parallel with almost no modification:
int sum = numbers.parallelStream().reduce(0, Integer::sum);
Reduction parallellizes well because the implementation can operate on subsets of the data in parallel, and then combine
the intermediate results to get the final correct answer. (Even if the language had a "parallel foreach" construct,
the mutative accumulation approach would still required the developer to provide threadsafe updates to the shared
accumulating variable sum, and the required synchronization would then likely eliminate any performance gain from parallelism.)
Using reduce()
instead removes all of the burden of parallelizing the reduction operation, and the library
can provide an efficient parallel implementation with no additional synchronization required.
In its more general form, a reduce operation on elements of type <T> yielding a result of type <U> requires three parameters:
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
The identity element is both an initial seed value for the reduction and a default result if there are no input elements.
The accumulator function takes a partial result and the next element, and produces a new partial result.
The combiner function combines two partial results to produce a new partial result. (The combiner is necessary in parallel reductions, where the input is partitioned, a partial accumulation computed for each partition, and then the partial results are combined to produce a final result.)
The code to sum all the integers in a stream can be rewritten as follows:
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8); Integer sum = numbers.stream().reduce(0, (res, i) > res + i, // adds one element's value to partial result (part1, part2) > part1 + part2); // combine two partial results System.out.print("Sum of all integers in the stream : " + sum);