193. Writing a custom collector

Let's assume that we have the following Melon class and List of Melon:

public class Melon {

private final String type;
private final int weight;
private final List<String> grown;

// constructors, getters, setters, equals(),
// hashCode(), toString() omitted for brevity
}

List<Melon> melons = Arrays.asList(new Melon("Crenshaw", 1200),
new Melon("Gac", 3000), new Melon("Hemi", 2600),
new Melon("Hemi", 1600), new Melon("Gac", 1200),
new Melon("Apollo", 2600), new Melon("Horned", 1700),
new Melon("Gac", 3000), new Melon("Hemi", 2600));

In the Partitioning section, we saw how to use the partitioningBy() collector to partition melons that weigh 2,000 g with duplicates:

Map<Boolean, List<Melon>> byWeight = melons.stream()
.collect(partitioningBy(m -> m.getWeight() > 2000));

Now, let's see if we can achieve the same result via a dedicated custom collector.

Let's begin by saying that writing a custom collector is not a day-to-day task, but it may be useful to know how to do it. The built-in Java Collector interface looks as follows:

public interface Collector<T, A, R> {
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
BinaryOperator<A> combiner();
Function<A, R> finisher();
Set<Characteristics> characteristics();
...
}

To write a custom collector, it is very important to know that T, A, and R represent the following:

  • T represents the type of elements from the Stream (elements that will be collected).
  • A represents the type of object that was used during the collection process known as the accumulator, which is used to accumulate the stream elements in a mutable result container.
  • R represents the type of the object after the collection process (the final result).

A collector may return the accumulator itself as the final result or may perform an optional transformation on the accumulator to obtain the final result (perform an optional final transformation from the intermediate accumulation type, A,to the final result type, R).

In terms of our problem, we know that T is Melon, A is Map<Boolean, List<Melon>>, and R is Map<Boolean, List<Melon>>. This collector returns the accumulator itself as the final result via Function.identity(). That being said, we can start our custom collector as follows:

public class MelonCollector implements
Collector<Melon, Map<Boolean, List<Melon>>,
Map<Boolean, List<Melon>>> {
...
}

So, a Collector is specified by four functions. These functions are working together to accumulate entries into a mutable result container, and optionally perform a final transformation on the result. They are as follows:

  • Creating a new empty mutable result container (supplier())
  • Incorporating a new data element into the mutable result container (accumulator())
  • Combining two mutable result containers into one (combiner())
  • Performing an optional final transformation on the mutable result container to obtain the final result (finisher())

In addition, the behavior of the collector is defined in the last method, characteristics(). Set<Characteristics> may contain the following four values:

  • UNORDERED: The order of accumulating/collecting elements is not important for the final result.
  • CONCURRENT: The elements of the stream can be accumulated by multiple threads in a concurrent fashion (in the end, the collector can perform a parallel reduction of the stream. The containers resulting from the parallel processing of the stream are combined in a single result container. The source of data should be unordered by its nature or the UNORDERED flag should be present.
  • IDENTITY_FINISH: Indicates that the accumulator itself is the final result (basically, we can cast A to R); in this case, the finisher() is not called.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.26.22