Java Stream Reduce

--

In this article, we’ll go through how you can use the Java Stream Reduce method of Stream API.

1. Java Stream Reduce Overview

In general, reduce methods are used to combine multiple values into one result; Java stream.reduce() does the same. Also, note that unlike map() and filter(), reduce() is a terminal operation so you cannot apply any additional stream operations.

Before we begin the examples, let’s go through some prerequisite information first.

2. BinaryOperator and BiFunction Overview

2.1 What is a BinaryOperator<T>

BinaryOperator is a function that accepts 2 parameters of the same type and returns a value that is the same type as the parameters.

2.2 What is a BiFunction<T, U, R>

BiFunction is a function that accepts a parameter of type T, a parameter of type U, and returns a value of type R.

3. BinaryOperator and BiFunction syntax

3.1 One-line Lambda

(a, b) -> c

Both a,b, and c must be of the same type when it comes to BinaryOperator while BiFunction does not have any restriction unless it is stated explicitly.

Of course, c should be produced by combining a and b using some operations.

3.2 Method Reference

Object_class_::a_method

Assuming that we have a class A, and a method a_method inside this class, then the method a_method should have one of the following signatures:

A a_method (A var){
// combine this object with var to produce a new result
return result;
}

OR

A a_method (A var1, A var2){
// combine var1 and var2 to produce a new result
return result;
}

Note that Method Reference can always be replaced by one-line lambda while the opposite is not true.

3.3 Multiline Lambda

(variable1, variable2)  -> {
lines_of_code;
...
return result;
}

The above will perform some operations before it returns the result. Note that this cannot be replaced by method reference without calling an external method.

4. Java Stream Reduce Examples

Now that we have established how BinaryOperator and BiFunction work, let's explain all three versions of stream.reduce() method with examples.

4.1 Optional<T> reduce(BinaryOperator<T> accumulator)

This method accepts only a BinaryOperator parameter and returns an optional of the same type. Consider the following example:

List<String> clh = Arrays.asList("Code", "Learn", "Hub");
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

String result = clh.stream().reduce(String::concat).get();
//exactly the same as above
// String result = clh.stream().reduce((s1, s2) -> s1.concat(s2)).get());

int sum = numbers.stream().reduce(Integer::sum).get();
//exactly the same as above
// int sum = numbers.stream().reduce((n1, n2) -> n1 + n2).get();

System.out.println("Reduce on list of strings : " + result);
System.out.println("Reduce on list of ints : " + sum);

Let’s take the clh.stream().reduce(String::concat).get(); and go step by step:

First iteration: ("Code", "Learn") -> "Code".concat("Learn"). This will return "CodeLearn"

Second iteration: ("CodeLearn", "Hub") -> "CodeLearn".concat("Hub"). This will return "CodeLearnHub"

The result returned will be wrapped as Optional so we use the get() method to retrieve the underlying value.

The snippet above will print the following:

Reduce on list of strings : CodeLearnHub
Reduce on list of ints : 15

4.2 T reduce(T identity, BinaryOperator<T> accumulator)

The difference with the previous method is that:

  1. It accepts an identity. You can think of identity as the initial value of the reduction
  2. It does not return an optional but the value itself

Consider this example:

List<String> clh = Arrays.asList("Code", "Learn", "Hub");
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

String result = clh.stream().reduce("Website:", String::concat);

int sum = numbers.stream().reduce(100, Integer::sum);

System.out.println("Reduce on list of strings : " + result);
System.out.println("Reduce on list of ints : " + sum);

Let’s take the numbers.stream().reduce(100, Integer::sum); and go step by step:

First iteration: (100, 1) -> 100 + 1. This will return 101

Second iteration: (101, 2) -> 101 + 2. This will return 103

Third iteration: (103, 3) -> 103 + 3. This will return 106

Fourth iteration: (106, 4) -> 106 + 4. This will return 110

Fifth iteration: (110, 5) -> 110 + 5. This will return 115

The full output is the following:

Reduce on list of strings : Website:CodeLearnHub
Reduce on list of ints : 115

4.3 <U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner)

The difference with the previous method is that it accepts not only a BinaryOperator function but also a BiFunction. As a result, this allows us to change the type of the result while with the previous one we couldn't.

Also note that these:

  • value to be returned
  • the identity
  • the first parameter and the return type of BiFunction
  • The parameters and the return type of the BinaryOperator

Must have the same type.

Consider an example that will convert eventually a stream of integers to a single result of String type:

List<Integer> clh = Arrays.asList(1, 2, 3);
String result = clh.stream()
.reduce("String is: ",
(firstNumber, secondNumber) -> firstNumber + secondNumber,
String::concat
);
System.out.println(result);

Let’s go step by step through the process:

First Iteration: ("1", 2) -> "1" + 2. It returns "12"

Second Iteration: ("12", 3) -> "12" + 3. It returns "123"

Now that the accumulator function has returned the value, the combiner function takes place and applies the following using the identity as the first parameter and the result of the accumulator function as the second parameter:

Combiner Function: ("String is: ", "123") -> "String is:".concat("123"). So it returns "String is: 123" which is the result to be printed.

5. Conclusion

By now you should know exactly how each version of stream.reduce() works. You can find the source code on my GitHub page.

6. Sources

[1]: Stream (Java SE 12 & JDK 12 ) — Oracle Help Center

--

--

Georgios Nikolaos Palaiologopoulos

Experienced Java Developer | Focused on Backend Software Development with Java & Spring Boot | Technical Writer