wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

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() or sorted(). 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 use map() and flatMap() 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 finally reduce() and collect().
    A typical way to use collect() is in conjunction with the Collectors static class, that provides the most commonly needed methods like toSet(), toList(), joining() or groupingBy(). 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 on 01 January 2021 | Comments (0) | categories: Java

Comments

  1. No comments yet, be the first to comment