Collecting Java Streams
I wrote about Java Streams before, sharing how they work for me and how, in conjunction with Java's functional interfaces, they enable us to write clean(er) code. I'd like to revisit my learnings, with some focus on the final step: what happens at the tail end of a stream operation
Four activities
There are four activities around Java Streams:
-
Create: There are numerous possibilities to create a stream. The most prevalent, I found, is
Collection.stream()
which returns a stream of anything in Java's collection framework: Collections, Lists, Sets etc.
There are more possibilities provided by the Stream interface, the StreamBuilder interface, the StreamSupport utility class or Java NIO's Files (and probably some more) -
Select: You can
filter()
,skip()
,limit()
,concat()
,distinct()
orsorted()
. All those methods don't change individual stream members, but determine what elements will be processed further. Selection and manipulation can happen multiple times after each other -
Manipulate: Replace each member of the stream with something else. That "something" can be the member itself with altered content. Methods that are fluent fit here nicely (like
stream().map(customer -> customer.setStatus(newStatus))
. We usemap()
andflatMap()
for this step. While it is perfectly fine to use Lambda Expressions, consider moving the Lambda body into its own function, to improve reading and debugging -
Collect: You can "collect" a stream once. After that it becomes inaccessible. The closest to classic loops here is the
forEach()
method, that allows you operate on the members as you are used to from the Java Collection framework.
Next are the convenience methods:count()
,findAny()
,findFirst()
,toArray()
and finallyreduce()
andcollect()
.
A typical way to usecollect()
is in conjunction with the Collectors static class, that provides the most commonly needed methods liketoSet()
,toList()
,joining()
orgroupingBy()
. Check the JavaDoc, there are 37 methods at your disposal.
However, sometimes, you might have different needs for your code, there custom collectors shine
For more details check out Baeldung's Java8 Stream Tutorial
Custom Collectors
Collector is a Java interface, so nothing stops us, short of understanding it, to implement our own. The interface heavily relies on Java's functional interfaces.
The lifecycle of a collector:
- Internal result holder gets created:
supplier()
-Supplier
- Stream members get accepted:
accumulator()
-BiConsumer
- Results of parallel operations get merged:
combiner()
-BinaryOperator
- (Optional) final transformation of internal result:
finisher()
-Function
- result is returned
The Collector.of()
method makes it easy to quickly assemble your collector
Examples
I'm using the Eclipse Vert.x framework for some of the examples, in case you wonder where Buffer
or JsonObject
come from.
Have all tasks been completed?
This collector returns true on either an empty stream or when all members are true
public static Collector<Boolean, Boolean, Boolean> allTrueCollector() {
return Collector.of(() -> Boolean.TRUE,
Boolean::logicalAnd,
Boolean::logicalAnd);
}
Use:
boolean amIdone = TaskSupplier.getTodayTasks().stream()
.map(t -> t.completed())
.collect(CustomCollectors.allTrueCollector());
The example is a little silly, you could achive the same result like this:
boolean amIdone = TaskSupplier.getTodayTasks().stream()
.filter(t -> !t.completed())
.isEmpty();
Build up a Json Object result
Your code retrieves different JsonObjects in a stream that need to be combined into a single result. E.g. searched against multiple data sources
public static Collector<JsonObject, JsonObject, JsonObject> jsonCollector() {
return Collector.of(JsonObject::new,
JsonObject::mergeIn,
JsonObject::mergeIn);
}
You have your own collection type
Example here uses JsonArray
which is like an ArrayList for Json
public static Collector<JsonObject, JsonArray, JsonArray> jsonObject2ArrayCollector() {
return Collector.of(JsonArray::new,
JsonArray::add,
JsonArray::addAll);
}
Total custom collector
Go wild!
public static Collector<TaskEntries, DraftReport, PdfReport> taskReportCollector(final Project project, final Team team) {
Supplier<DraftReport> draft = DraftReportTemplates.someMethod(team);
BiConsumer<DraftReport, TaskEntries> accumulator = team.someMethod(project);
return Collector.of(draft,
accumulator,
DraftReport::merge,
draft -> PdfGenerator.generate(team, draft));
}
Hope this gives you some ideas, as usual YMMV
Posted by Stephan H Wissel on 01 January 2021 | Comments (0) | categories: Java