Writing a custom collector that collects into a LinkedHashSet – Functional style programming – extending API
Writing a custom collector that collects into a LinkedHashSet
In a custom collector that collects into a LinkedHashSet we have that the supplier is LinkedHashSet::new, the accumulator is HashSet::add, the combiner relies on HashSet#addAll(), and the finisher is the identity function:
public static <T> Collector<T, LinkedHashSet<T>,
LinkedHashSet<T>> toLinkedHashSet() {
return Collector.of(LinkedHashSet::new, HashSet::add,
(left, right) -> {
left.addAll(right);
return left;
}, Collector.Characteristics.IDENTITY_FINISH);
}
In the following example, we use this collector to collect the sorted cars’ horsepower:
LinkedHashSet<Integer> hpSorted = cars.values().stream()
.map(c -> c.getHorsepower())
.sorted()
.collect(MyCollectors.toLinkedHashSet());
Done! The LinkedHashSet<Integer> contains the horsepower values in ascending order.
Writing a custom collector that excludes elements of another collector
The goal of this section is to provide a custom collector that takes as arguments a Predicate and a Collector. It applies the given predicate to elements to be collected in order to exclude the failures from the given collector.
public static <T, A, R> Collector<T, A, R> exclude(
Predicate<T> predicate, Collector<T, A, R> collector) {
return Collector.of(
collector.supplier(),
(l, r) -> {
if (predicate.negate().test(r)) {
collector.accumulator().accept(l, r);
}
},
collector.combiner(),
collector.finisher(),
collector.characteristics()
.toArray(Collector.Characteristics[]::new)
);
}
The custom collector uses the supplier, combiner, finisher, and characteristics of the given collector. It only influences the accumulator of the given collector. Basically, it explicitly calls the accumulator of the given collector only for the elements that pass the given predicate.For instance, if we want to obtain the sorted horsepower less than 200 via this custom collector then we call it as follows (the predicate tells what to exclude):
LinkedHashSet<Integer> excludeHp200 = cars.values().stream()
.map(c -> c.getHorsepower())
.sorted()
.collect(MyCollectors.exclude(c -> c > 200,
MyCollectors.toLinkedHashSet()));
Here, we use two custom collectors, but we can easily replace the toLinkedHashSet() with a built-in collector as well. Challenge yourself to write the counterpart of this custom collector. Write a collector that includes the elements that pass the given predicate.
Writing a custom collector that collects elements by type
Let’s suppose that we have the following List<Vechicle>:
Vehicle mazda = new Car(“Mazda”, “diesel”, 155);
Vehicle ferrari = new Car(“Ferrari”, “gasoline”, 500);
Vehicle hov = new Submersible(“HOV”, 3000);
Vehicle rov = new Submersible(“ROV”, 7000);
List<Vehicle> vehicles = List.of(mazda, hov, ferrari, rov);
Our goal is to collect only the cars or only the submersibles, but not both. For this, we can write a custom collector that collects by type into the given supplier as follows:
public static
<T, A extends T, R extends Collection<A>> Collector<T, ?, R>
toType(Class<A> type, Supplier<R> supplier) {
return Collector.of(supplier,
(R r, T t) -> {
if (type.isInstance(t)) {
r.add(type.cast(t));
}
},
(R left, R right) -> {
left.addAll(right);
return left;
},
Collector.Characteristics.IDENTITY_FINISH
);
}
Now, we can collect only the cars from List<Vechicle> into an ArrayList as follows:
List<Car> onlyCars = vehicles.stream()
.collect(MyCollectors.toType(
Car.class, ArrayList::new));
And, we can collect only the submersible into a HashSet as follows:
Set<Submersible> onlySubmersible = vehicles.stream()
.collect(MyCollectors.toType(
Submersible.class, HashSet::new));
Finally, let’s write a custom collector for a custom data structure.
Writing a custom collector for Splay Tree
In Chapter 5, Problem 120, we have implemented a Splay Tree data structure. Now, let’s write a custom collector capable to collect elements into a Splay Tree. Obviously, the supplier is SplayTree::new. Moreover, the accumulator is SplayTree#insert(), while the combiner is SplayTree#insertAll():
public static
Collector<Integer, SplayTree, SplayTree> toSplayTree() {
return Collector.of(SplayTree::new, SplayTree::insert,
(left, right) -> {
left.insertAll(right);
return left;
},
Collector.Characteristics.IDENTITY_FINISH);
}
Here is an example that collects the car’s horsepower into a SplayTree:
SplayTree st = cars.values().stream()
.map(c -> c.getHorsepower())
.collect(MyCollectors.toSplayTree());
Done! Challenge yourself to implement a custom collector.
Leave a Reply