Learnitweb

Stream.reduce() in Java

1. Introduction

While working with Java streams, there are many terminal operations (such as sum, min, max, count and average) that return one value by combining the contents of a stream. These operations are called reduction operations.

Reduction operations are not limited to returning a single value. Reduction operations can return a collection as well. An operation which groups elements into categories is also an example of reduction operation. There are two general purpose reduction operations provided by JDK: reduce and collect. In this tutorial, we’ll discuss reduce operation.

2. The Stream.reduce() method

There are 3 overloaded versions of reduce method:

  1. Optional<T> reduce(BinaryOperator<T> accumulator): This method uses an associative accumulation function to perform reduction and return a reduced value, if any.
  2. T reduce(T identity, BinaryOperator<T> accumulator): This method performs a reduction using the provided identity value and an and an associative accumulation function, and returns the reduced value.
  3. <U> reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner): This method performs a reduction on the elements of this stream, using the provided identity, accumulation and combining functions.

Let us understand the reduce() method with the help of some code. Following is an example of getting sum of elements of a list:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 
int sum = numbers.parallelStream()
          .mapToInt(Integer::intValue)
          .sum();

The same code can be written using the reduce() method as:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int sum = numbers.parallelStream()
          .mapToInt(Integer::intValue)
          .reduce(
					0,
					(a, b) -> a + b
				);

In this example, the reduce method takes two arguments:

  1. identity: The identity element is the initial element and it is the default value in case there is no element in the stream. In this example, the initial value is 0 which is the sum if there are no elements in the stream.
  2. accumulator: The accumulator function receives two arguments: a partial result from the reduction process (in this case, the sum of all integers processed so far) and the next element in the stream (an integer in this instance). It then produces a new partial result. In this example, the accumulator function is represented by a lambda expression that takes two Integer values, adds them together, and returns the resulting Integer value.

Note: The reduce operation always returns a new value. However, the accumulator function also returns a new value every time it processes an element of a stream. This is a performance issue if you are working with a collection because every time an element is processed, your accumulator will create a new collection.

3. Example 2

Following is an example of creating a comma-separated string from string elements of a list.

import java.util.Arrays;
import java.util.Optional;

class ReduceExample {
    public static void main(String[] args) {

        // String array
        String[] array = { "one", "two", "three", "four", "five" };

        Optional<String> reducedString = Arrays.stream(array)
                .reduce((str1, str2)
                        -> str1 + "," + str2);

        // Displaying the combined String
        if (reducedString.isPresent()) {
            System.out.println(reducedString.get());
        }
    }
}

4. Handling exceptions in reducer

It is possible for a reducer function to throw an exception. Let us understand this with an example. Following code is going to throw ArithmeticException as the divider is 0.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
int divider = 0;
int result = numbers.stream().reduce(0, (a, b) -> a / divider + b / divider);

This can be handled by using try-catch block.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
        int divider = 0;
        int result = numbers.stream().reduce(0, (a, b) -> {
                    try {
                        return a / divider + b / divider;
                    } catch (ArithmeticException e) {
                       e.printStackTrace();
                    }
                    return 0;
 });

Using try-catch block in lambda doesn’t look very nice. There is a better way to write this piece of code. We can move this core operation in a separate method.

 private static int divide(int value, int divisor) {
    int result = 0;
    try {
        result = value / divisor;
    } catch (ArithmeticException e) {
        e.printStackTrace();
    }
    return result;
}

The complete code of the class, looks like the following:

class ReduceExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
        int divider = 0;
        int result = numbers.stream().reduce(0, (a, b) -> divide(a, divider) + divide(b, divider));
    }

    private static int divide(int value, int divisor) {
        int result = 0;
        try {
            result = value / divisor;
        } catch (ArithmeticException e) {
            e.printStackTrace();
        }
        return result;
    }
}

5. Using complex objects in reducer

Till now, we have used only primitives in the examples. However, we can use complex objects also in reducing operations.

Following is an example of getting the sum of age of all employees in a team. Here, employeeList is a list of Employee objects.

        Integer totalAge = employeeList
                .stream()
                .map(Employee::getAge)
                .reduce(
                        0,
                        (a, b) -> a + b);

6. Conclusion

The Stream.reduce method in Java is a powerful tool for performing aggregation operations on a stream of elements. It enables you to combine the elements of a stream into a single result using an associative accumulation function. Whether you’re summing numbers, concatenating strings, or merging data structures, reduce provides a flexible and expressive way to achieve your goals.

Understanding how to use the reduce method effectively involves recognizing the roles of the identity value, accumulator function, and combiner function in parallel operations. By mastering these concepts, you can leverage reduce to write concise, readable, and efficient code for complex data processing tasks.