Implementing Mutable Reduction: The collect() Method – Streams
By Stephen Trude / August 23, 2022 / No Comments / Certifications of Oracle, Multilevel Partitioning, Numeric Optional Classes
Implementing Mutable Reduction: The collect() Method
The collect(Collector) method accepts a collector that encapsulates the functions required to perform a mutable reduction. We discuss predefined collectors implemented by the java.util.stream.Collectors class in a later section (p. 978). The code below uses the collector returned by the Collectors.toList() method that accumulates the result in a list (p. 980).
List<String> titles = CD.cdList.stream()
.map(CD::title).collect(Collectors.toList());
// [Java Jive, Java Jam, Lambda Dancing, Keep on Erasing, Hot Generics]
The collect(supplier, accumulator, combiner) generic method provides the general setup for implementing mutable reduction on stream elements using different kinds of mutable containers—for example, a list, a map, or a StringBuilder. It uses one or more mutable containers to accumulate partial results that are combined into a single mutable container that is returned as the result of the reduction operation.
<R,A> R collect(Collector<? super T,A,R> collector)
This terminal operation performs a reduction operation on the elements of this stream using a Collector (p. 978).
A Collector encapsulates the functions required for performing the reduction.
The result of the reduction is of type R, and the type parameter A is the intermediate accumulation type of the Collector.
<R> R collect(
Supplier<R> supplier,
BiConsumer<R,? super T> accumulator,
BiConsumer<R,R> combiner)
This terminal operation performs a mutable reduction on the elements of this stream. A counterpart to this method is also provided for numeric streams.
The supplier creates a new mutable container of type R—which is typically empty. Elements are incorporated into such a container during the reduction process. For a parallel stream, the supplier can be called multiple times, and the container returned by the supplier must be an identity container in the sense that it does not mutate any result container with which it is merged.
The accumulator incorporates additional elements into a result container: A stream element of type T is incorporated into a mutable container of type R.
The combiner merges two values that are mutable containers of type R. It must be compatible with the accumulator. There is no guarantee that the combiner is called if the stream is sequential, but definitely comes into play if the stream is parallel.
Both the accumulator and the combiner must also be non-interfering and stateless functions (p. 909).
With the above requirements on the argument functions fulfilled, the collect() method will produce the same result regardless of whether the stream is sequential or parallel.
We will use Figure 16.13 to illustrate mutable reduction performed on a sequential stream by the three-argument collect() method. The figure shows both the code and the execution of a stream pipeline to create a list containing the number of tracks on each CD. The stream of CDs is mapped to a stream of Integers at (3), where each Integer value is the number of tracks on a CD. The collect() method at (4) accepts three functions as arguments. They are explicitly defined as lambda expressions to show what the parameters represent and how they are used to perform mutable reduction. Implementation of these functions using method references can be found in Example 16.12.
- Supplier: The supplier is a Supplier<R> that is used to create new instances of a mutable result container of type R. Such a container holds the results computed by the accumulator and the combiner. In Figure 16.13, the supplier at (4) returns an empty ArrayList<Integer> every time it is called.
- Accumulator: The accumulator is a BiConsumer<R, T> that is used to accumulate an element of type T into a mutable result container of type R. In Figure 16.13, type R is ArrayList<Integer> and type T is Integer. The accumulator at (5) mutates a container of type ArrayList<Integer> by repeatedly adding a new Integer value to it, as illustrated in Figure 16.13b. It is instructive to contrast this accumulator with the accumulator for sequential functional reduction illustrated in Figure 16.11, p. 957.
- Combiner: The combiner is a BiConsumer<R, R> that merges the contents of the second argument container with the contents of the first argument container, where both containers are of type R. As in the case of the reduce(identity, accumulator, combiner) method, the combiner is executed when the collect() method is called on a parallel stream.
Figure 16.13 Sequential Mutable Reduction