Pages

Footer Pages

Spring Boot

Java String API

Java Conversions

Kotlin Programs

Kotlin Conversions

Java Threads Tutorial

Java 8 Tutorial

Friday, November 26, 2021

Java 8 Stream reduce

1. Overview

In this tutorial, We'll learn how to use reduce() method to produce the final result from the stream in java 8.

Stream API is added in java 8 and now reduce operation is heavily used by the developers.

reduce() method is a terminal operation of streams.

Java 8 Stream reduce



2. Stream.reduce() Syntax


reduce() method performs the reduction operation on the stream and returns a single value wrapping inside Optional.

reduce() is an overloaded method with different types of arguments to handle the different use cases.
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)


3. Java Stream reduce() Arguments


reduce() methods take 3 arguments at most and they are accumulator, identity and Combiner.

accumulator: This takes two arguments. The first argument stores the partial result of the reduce operation and the second argument stores the next value from the stream.

identity: This is the initial value of the reduce operation. Most of it starts with the default value of stream value type. If we want to start with non default value then we need to use identity.

combiner: If you have an idea about Hadoop map reduce jobs then you may have an idea of the usage of the combiner. But, if you do not know about the map reduce then do not worry. we will learn this with the example program.

When the stream execution is parallel then we might have to use the reduce() combiner option to speed up and collect the intermediate reduce results then pass them to the combiner logic.

In most cases, reduction accumulator logic and combiner logic is the same.


4. Stream reduce() examples


First, understand the example of reduce() method with only accumulator logic.

We use the reduce() method with only one argument that is an accumulator.

Example 1
package com.javaprogramto.java8.streams.reduce;

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

public class StreamReduceExample {

	public static void main(String[] args) {

		List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

		Optional<Integer> sumOptional = numbers.stream().reduce((currentSum, nextValue) -> currentSum + nextValue);

		if(sumOptional.isPresent()) {
			System.out.println("sum of first 7 numbers is "+sumOptional.get());
		}
	}

}

Output:

sum of first 7 numbers is 28


Here the numbers list has values from 1 to 7 and performed the addition of all numbers of list using reduce() function.

reduce accumulator first argument took the current sum and 2nd argument holds next value from the stream.

look at the each step of itreation of reduce() method with currentSum and nextValue.
currentSum nextValue
  0                      1
  1                      2
  3                      3
  6                      4
  10                    5
  15                    6
  21                    7
  28 is return as optional object.



5. Stream reduce() example with initial value


Sometimes we may need to pass the initial value to the reduce operation. In that case, we need to use the identity argument of reduce() function.

We use now the reduce() method with 2 arguments and that identity and accumulator.

Example 2

In this example, create the list with only 3 values and those values are 8, 9, 10.

Now, we need to find the sum of the first 10 numbers but the list has only 3 numbers from 8 to 10. The remaining numbers from 1 to 7 are missing. So, we are storing the sum of 1 to 7 is 28 in the integer variable.

Now we pass this integer to reduce the method to start the sum from 28 rather than 0 as the default value.

Look at the below example.
package com.javaprogramto.java8.streams.reduce;

import java.util.Arrays;
import java.util.List;

public class StreamReduceExample2 {

	public static void main(String[] args) {

		Integer sumOf1to7 = 28;

		List<Integer> numbers = Arrays.asList(8, 9, 10);

		Integer sum = numbers.stream().reduce(sumOf1to7, (currentSum, nextValue) -> currentSum + nextValue);

		System.out.println("sum of first 10 numbers is " + sum);
	}

}

Output:
sum of first 10 numbers is 55

In the output, we are seeing the sum is 55 which is a sum of first 10 numbers using reduce() default value option.

In this case, reduce() method does not return the Optional instance rather than it returns the type of identity or default value or initial value type. Because the initial value will be with some value always. If the default value is null then it is not suggested to use reduce identity method. You need to use the reduce with the accumulator only.


5. Stream reduce() Parallel example with Combiner


Next, we use reduce() method 3 arguments - identity, accumulator and combiner.

Example 3

Look at the below example stream is sequential, with and without combiner usage.

package com.javaprogramto.java8.streams.reduce;

import java.util.Arrays;
import java.util.List;

public class StreamReduceExample3 {

	public static void main(String[] args) {

		Integer sumOf1to2 = 3;

		List<Integer> numbers = Arrays.asList(3, 4, 5, 6, 7, 8, 9, 10);

		Integer sum = numbers.stream().reduce(sumOf1to2, (currentSum, nextValue) -> currentSum + nextValue);

		System.out.println("Without combiner - sum of first 10 numbers is " + sum);

		Integer sumWithCombiner = numbers.stream().reduce(sumOf1to2, (currentSum, nextValue) -> currentSum + nextValue,
				Integer::sum);

		System.out.println("With Combiner - sum of first 10 numbers is " + sumWithCombiner);

	}
}

Output

Without combiner - sum of first 10 numbers is 55
With Combiner - sum of first 10 numbers is 55

Output is the same when the stream is executed in sequential.

Note: When the stream is not parallel then no need of using a combiner in the reduce() operation.

Let us use reduce() method with the parallel streams with and without combiner.

Example 4

In this example, we are getting the addition of the first 100 million numbers with sequential and parallel streams using reduce combiner logic.


This is quite huge processing and you can see the performance benchmarks.
package com.javaprogramto.java8.streams.reduce;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class StreamReduceExample4 {

	public static void main(String[] args) {

		Integer identity = 0;

		
		IntStream numbers = IntStream.iterate(1, i -> i + 1);

		List<Integer> numList = numbers.limit(100000000).boxed().collect(Collectors.toList());

		long l1 = System.currentTimeMillis();
		Integer sum = numList.stream().reduce(identity, (currentSum, nextValue) -> currentSum + nextValue, Integer::sum);
		long l2 = System.currentTimeMillis();
		System.out.println("With sequential stream sum is " + sum + " in " + (l2 - l1) + " ms");

		
		long l3 = System.currentTimeMillis();
		Integer sumWithCombiner = numList.stream().parallel().reduce(identity,
				(currentSum, nextValue) -> currentSum + nextValue, Integer::sum);
		long l4 = System.currentTimeMillis();
		
		System.out
				.println("With parallel stream sum is " + sumWithCombiner + " in " + (l4 - l3) + " ms");

	}
}


Output:
With sequential stream sum is 987459712 in 1861 ms
With parallel stream sum is 987459712 in 549 ms

Parallel streams took 3 times less execution time than sequential stream execution. So, parallel streams are always more performant than normal streams.


6. Stream reduce() exceptional handling


The below program throws the runtime arithmetic exception.

Example 5

package com.javaprogramto.java8.streams.reduce;

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

public class StreamReduceExample5 {

	public static void main(String[] args) {

		List<Integer> numbers = Arrays.asList(1, 2, 3, 0, 5, 6, 7);

		Optional<Integer> division = numbers.stream().reduce((currentSum, nextValue) -> currentSum / nextValue);

		if (division.isPresent()) {
			System.out.println("division of first 7 numbers is " + division.get());
		}
	}
}

Output
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at com.javaprogramto.java8.streams.reduce.StreamReduceExample5.lambda$0(StreamReduceExample5.java:13)
	at java.base/java.util.stream.ReduceOps$2ReducingSink.accept(ReduceOps.java:123)
	at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
	at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.reduce(ReferencePipeline.java:558)
	at com.javaprogramto.java8.streams.reduce.StreamReduceExample5.main(StreamReduceExample5.java:13)


To handle arithmetic exception, we need to add the try-catch inside the lambda expression as below. But the code does not look clean.

Example 6

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

import org.slf4j.Logger;


public class StreamReduceExample5 {
	
	static Logger log = org.slf4j.LoggerFactory.getLogger(StreamReduceExample5.class);

	public static void main(String[] args) {

		List<Integer> numbers = Arrays.asList(1, 2, 3, 0, 5, 6, 7);

		Optional<Integer> division = numbers.stream().reduce((currentSum, nextValue) -> {
			int div = 0;
			try {
			div = currentSum / nextValue;
			} catch (ArithmeticException e) {
				log.error("arithmetic exception thrown");
			}
			return div;
		});

		if (division.isPresent()) {
			System.out.println("division of first 7 numbers is " + division.get());
		}
	}
}

To make the code cleaner, we need to use the separate utility method and call the method from lambda.

Example 7

public class StreamReduceExample6 {

	static Logger log = org.slf4j.LoggerFactory.getLogger(StreamReduceExample5.class);

	public static void main(String[] args) {

		List<Integer> numbers = Arrays.asList(1, 2, 3, 0, 5, 6, 7);

		Optional<Integer> division = numbers.stream().reduce((currentSum, nextValue) -> doDivision(currentSum, nextValue));

		if (division.isPresent()) {
			System.out.println("division of first 7 numbers is " + division.get());
		}
	}

	private static int doDivision(Integer currentSum, Integer nextValue) {
		int div = 0;
		try {
			div = currentSum / nextValue;

		} catch (ArithmeticException e) {
			log.error("arithmetic exception thrown");
		}
		return div;
	}
}



7. Stream reduce() - with custom objects


reduce() method can work with custom complex objects.

Example 8

package com.javaprogramto.java8.streams.reduce;

import java.util.Arrays;
import java.util.List;

import com.javaprogramto.java8.compare.Employee;

public class StreamReduceExample7 {

	public static void main(String[] args) {

		List<Employee> empList = Arrays.asList(new Employee(100, "A", 20), new Employee(200, "B", 30),
				new Employee(300, "C", 40));

		Integer ageSum = empList.stream().map(emp -> emp.getAge()).reduce(new Employee(400, "new emp", 60).getAge(),
				(age1, age2) -> age1 + age2);

		System.out.println("age sum - " + ageSum);

	}

}

Output
age sum - 150


8. Conclusion


In this article, We've seen in-depth about stream reduce() method in java 8.




No comments:

Post a Comment

Please do not add any spam links in the comments section.