Reduce in Java Stream

Java Stream: Reduce Method

Gene Zeiniss
The Startup

--

Whatever doubts I had, I’ve decided to dedicate this post to the Java stream’s reduce() operation. “Such a strange opening”, you might think. Full disclosure: reduce() is my least favorite stream’s method, and that’s a fact. However, there is at least one challenge that, in my opinion, this method is the best choice for solving. For now, I’m going to call it “Fail Fast”.

First: Java stream reduction overview.

Java Stream Reduction

Stream reduction is an operation that returns one value by combining the elements of a stream. The Java stream API contains a set of predefined reduction operations, such as average(), sum(), min(), max(), and count(). There is one general-purpose operation: reduce().

.reduce()

reduce() is a method for generating custom reduction operations on a stream. “Custom operation” sounds appealing, to me anyway. When I heard about the method for the first time, I thought “freedom!” and fantasized about the endless possibilities we will make together. In the words of the famous poet: “Nana banana I do what I wanna do”. I googled it, of course, and was quite disappointed by the results I’ve found. The first results page included stocks of tutorials, but all of them taught how to deal with primitive objects. And each provided reduce() usage could have easily been replaced by other, more specific, and more readable methods.

Let’s go back to good old Contact that helped us in my previous posts. For the current topic’s examples I will add a“birthDate” property to the class:

Suppose you want to calculate the sum of all contacts’ ages, for some (I can’t imagine) reason. So, you can use the reduce(T identity, BinaryOperator<T> accumulator) method where

identity” is the initial value of the reduction operation and the default result if the stream is empty. In other words, “what to start with”. In our case, we will start with 0;

accumulator” is a function for combining two values. Or, “what to do with”. In our case, it should sum values.

According to documentation, this is what a reduce(T identity, BinaryOperator<T> accumulator) looks like under the hood:

Back to our contacts stream. The code should look like this:

By the way, IntelliJ will suggest replacing the lambda expression with a method reference. The replacement will look like follows:

Now, in the last pieces of code, you already can recognize the sum() method that is applied on an Integer type. Now, here’s not a newsflash: the sum() method can be used as an independent method as well. The code looks clearer, but with different strokes…

Can you see it? My point is that if you can replace the method with an internal one, why bother with reduce() it at all? I found my own use-case: a promise to fail fast.

Fail Fast

In my last post, I showed you the potential bug that can be caused by using “findes” methods. In brief: we filtered existing contacts stream by email and used “findes” to retrieve only the desired one. However, findFirst() and findAny() methods cannot express the assumption that there is a single element left in the stream.

reduce() to the rescue! Check the reduce(BinaryOperator<T> accumulator) documentation. This kind of reduction loop is equivalent to:

Say we’re pretty sure that there’s at most one contact matching the provided email and we would like the code to fail fast if there isn’t one.

We can use a loop that will look as follows:

Take a look at both loops. They are almost identical. The only difference is the accumulator function in the reduce loop. I mean, all we need to replace our loop with a stream that includes reduce() action, is an accumulator that throws the DuplicateEmailException() as soon as it’s called:

We can make the code more readable by placing the accumulator in some utility class and giving it a meaningful name:

Then it will look like this:

I’m at peace with this code 🤓.

Conclusion

Despite being a custom reduction operation, most of the reduce() abilities can be replaced by predefined operations, such as average(), sum(), min(), max() and count(). That’s why this method is often neglected. Yet there are times when reduction should be customized. As was the case, when we wanted to make the code fail fast, if more than one element survived the stream filter.

I think I’ve made my point.

--

--

Gene Zeiniss
The Startup

My blog is about wide aspects of programming, from design to code review. Still, I have a predilection for coding, all comes from my unabashed love for Java ☕