In this article, we will discuss Stream’s reduce() method in detail with examples
1. Stream reduce() method :
- This Stream method is a terminal operation which performs reduction on the given stream and returns a reduced (or single) value
- There are 3 variants of reduce() method
- Method signature 1 :- Optional<T> reduce(BinaryOperator<T> accumulator)
- Method signature 2 :- T reduce(T identity, BinaryOperator<T> accumulator)
- Method signature 3 :- <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
- reduce() method helps to derive sum of the Stream, searching max/mix element amongst Stream elements or finding average and performing String concatenation
- We can utilize this method for Map and reduce functionality
2. Stream reduce() method with accumulator :
- This is the first variant of the reduce() method which takes only one argument BinaryOperator as accumulartor
- This accumulator perform reduction on the given Stream elements using Lambda expression or Method Reference
- Method signature :- Optional<T> reduce(BinaryOperator<T> accumulator)
- Throws NullPointerException if the result of the reduction is null
2.1 Summing Stream elements
- In this Stream reduce() example, we are going to summate all elements present in the Stream using Lambda Expression as well as Method Reference
- For lambda expression – reduce((m,n) -> m+n)
- For method reference – reduce(Integer::sum)
- Both these approaches returns OptionalInt
- We can easily check whether value is present using ifPresent() method and print to console, if present
package net.bench.resources.stream.reduce.example;
import java.util.Arrays;
import java.util.OptionalInt;
public class StreamReduceForSumUsingReduceAccumulator {
public static void main(String[] args) {
// primitive int[] array
int[] intArray = {
17, 23, 29, 31, 37, 41, 43, 47, 53, 58
};
// 1.1 reduce() - sum using Lambda Expression
OptionalInt optionalIntResult1 = Arrays
.stream(intArray)
.reduce((m,n) -> m+n); // summation
// 1.2 get value, if present
optionalIntResult1.ifPresent(
sum -> System.out.println("Stream.reduce() - "
+ "Sum using Lambda Expresion = "
+ sum));
// 2.1 reduce() - sum using Method Reference
OptionalInt optionalIntResult2 = Arrays
.stream(intArray)
.reduce(Integer::sum); // summation
// 2.2 get value, if present
optionalIntResult2.ifPresent(
sum -> System.out.println("Stream.reduce() - "
+ "Sum using Method Reference = "
+ sum));
}
}
Output:
Stream.reduce() - Sum using Lambda Expresion = 379
Stream.reduce() - Sum using Method Reference = 379
2.2 Searching maximum/minimum element from Stream
- In this Stream reduce() example, we are going to find greatest and smallest element from Stream using Lambda Expression as well as Method Reference
- For finding greatest/maximum element from Stream, use reduce((x, y) -> (x > y ? x : y)) for lambda expression and reduce(Integer::max) for method reference
- For finding smallest/minimum element from Stream, use reduce((x, y) -> (x < y ? x : y)) for lambda expression and reduce(Integer::min) for method reference
- All the above approaches returns OptionalInt which has a useful called ifPresent() to check whether value is present and we can print to console, if present
package net.bench.resources.stream.reduce.example;
import java.util.Arrays;
public class FindMinAndMaxUsingReduceAccumulator {
public static void main(String[] args) {
// primitive int[] array
int[] intArray = {
17, 23, 29, 31, 37, 41, 43, 47, 53, 59
};
// 1.1 reduce() - find Maximum element using Lambda Expression
Arrays
.stream(intArray)
.reduce((x, y) -> (x > y ? x : y)) // find greatest
.ifPresent(
max -> System.out.println("Stream.reduce() - "
+ "Maximum element using Lambda Expresion = "
+ max));
// 1.2 reduce() - find Maximum element using Method Reference
Arrays
.stream(intArray)
.reduce(Integer::max) // find greatest
.ifPresent(
max -> System.out.println("\nStream.reduce() - "
+ "Maximum element using Method Reference = "
+ max));
// 2.1 reduce() - find Minimum using Lambda Expression
Arrays
.stream(intArray)
.reduce((x, y) -> (x < y ? x : y)) // find smallest
.ifPresent(
min -> System.out.println("\n\nStream.reduce() - "
+ "Minimum element using Lambda Expresion = "
+ min));
// 2.2 reduce() - find Minimum using Method Reference
Arrays
.stream(intArray)
.reduce(Integer::min) // find smallest
.ifPresent(
min -> System.out.println("\nStream.reduce() - "
+ "Minimum element using Method Reference = "
+ min));
}
}
Output:
Stream.reduce() - Maximum element using Lambda Expresion = 59
Stream.reduce() - Maximum element using Method Reference = 59
Stream.reduce() - Minimum element using Lambda Expresion = 17
Stream.reduce() - Minimum element using Method Reference = 17
2.3 Finding average from Stream elements
- In this Stream reduce() example, we are going to find average from Stream elements
- To get average, we have to find summation of all elements using either Lambda Expression or Method Reference
- Then we have to divide this sum by number of elements present in the Stream which will return integer part discarding fractional part
- The above approach isn’t the optimal way, as it discards fractional portion thereby not providing accurate result
- Therefore, we can use average() method of IntStream to get average as double value which is the most accurate one
package net.bench.resources.stream.reduce.example;
import java.util.Arrays;
public class FindAverageUsingReduceAccumulator {
public static void main(String[] args) {
// int[] array
int[] intArray = {
17, 23, 29, 31, 37, 41, 43, 47, 53
};
// 1. Stream.reduce() - average using Lambda Expression
int average1 = Arrays
.stream(intArray)
.reduce((m,n) -> m+n)
.orElse(0) / intArray.length;
System.out.println("Stream.reduce() - "
+ "Average using Lambda Expression = "
+ average1);
// 2. Stream.reduce() - average using Method Reference
int average2 = Arrays
.stream(intArray)
.reduce(Integer::sum)
.orElse(0) / intArray.length;
System.out.println("\nStream.reduce() - "
+ "Average using Method Reference = "
+ average2);
// 3. using average() of IntStream
double average3 = Arrays
.stream(intArray)
.average()
.getAsDouble();
System.out.println("\nUsing average() of IntStream = "
+ average3);
}
}
Output:
Stream.reduce() - Average using Lambda Expression = 35
Stream.reduce() - Average using Method Reference = 35
Using average() of IntStream = 35.666666666666664
2.4 String Concatenation
- In this Stream reduce() example, we are going to concatenate all Strings present in the Stream using Lambda Expression as well as Method Reference
- For lambda expression use reduce((str1, str2) -> str1 + ” ” + str2) – this approach helps us to provide delimiter in between 2 String values while concatenating
- For method reference use reduce(String::concat) – this approach just helps us to concatenate all String together
- Both these approaches returns Optional<String>
- We can easily check whether value is present using ifPresent() method and print to console, if present
- There are 2 more use-cases, where in 1st use-case we are converting String into upper case and then concatenating with space delimiter and in the 2nd use-case we are concatenating with pipe (|) as delimiter between 2 String values
package net.bench.resources.stream.reduce.example;
import java.util.Arrays;
import java.util.Optional;
public class StringConcatenationUsingReduceAccumulator {
public static void main(String[] args) {
// String[] array
String[] strArray = {
"India",
"is",
"a",
"Beautiful",
"country"
};
// 1.1 Stream.reduce() - string concatenation using lambda
Optional<String> resultLEx = Arrays
.stream(strArray)
.reduce((str1, str2) -> str1 + " " + str2);
// print result, if present
resultLEx.ifPresent(System.out::println);
// 1.2 Stream.reduce() - string concatenation using method ref
Optional<String> resultMRef = Arrays
.stream(strArray)
.reduce(String::concat);
// print result, if present
resultMRef.ifPresent(System.out::println);
// 1.3 upper case and string concat
Arrays
.stream(strArray)
.reduce((str1, str2) -> str1.toUpperCase() + " " + str2.toUpperCase())
.ifPresent(System.out::println); // print to console
// 1.4 String concatenation with pipe | separator
Arrays
.stream(strArray)
.reduce((str1, str2) -> str1 + "|" + str2)
.ifPresent(System.out::println); // print to console
}
}
Output:
India is a Beautiful country
IndiaisaBeautifulcountry
INDIA IS A BEAUTIFUL COUNTRY
India|is|a|Beautiful|country
2.5 Throws NullPointerException when reduction is null
- Stream’s reduce() method with one argument throws NullPointerException when result of reduction is null
- Note: there is no possibility to provide initial value with this variant, therefore a better variant with initial/default value is discussed in the next section and this helps us to avoid NPE
package net.bench.resources.stream.reduce.example;
import java.util.Arrays;
public class ExceptionInReduceAccumulator {
public static void main(String[] args) {
// String[] array
String[] strArray = {null};
// string concatenate and print
Arrays
.stream(strArray)
.reduce((str1, str2) -> str1 + " " + str2) // throws NPE
.ifPresent(System.out::println);
}
}
Output:
Exception in thread "main" java.lang.NullPointerException
at java.util.Objects.requireNonNull(Objects.java:203)
at java.util.Optional.<init>(Optional.java:96)
at java.util.Optional.of(Optional.java:108)
at java.util.stream.ReduceOps$2ReducingSink.get(ReduceOps.java:129)
at java.util.stream.ReduceOps$2ReducingSink.get(ReduceOps.java:107)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.reduce(ReferencePipeline.java:479)
at net.bench.resources.stream.reduce.example.ExceptionInAccumulator
.main(ExceptionInAccumulator.java:15)
3. Stream reduce() method with Identity and accumulator :
- This is the second variant of the reduce() method which takes two arguments
1. Identity as initial/default value
2. BinaryOperator as accumulartor - Identity value will be used as a starting value while doing sum/multiply/subtraction/division operations
- And the same Identity value will be used as a default value when Stream is empty
- Note: by providing initial/default/starting value to the accumulator makes sure that reduce() won’t throw any exception like NullPointerException as in the case of 1st variant
- Like 1st variant, this accumulator also perform reduction on the given Stream elements using Lambda expression or Method Reference
- Method signature :- T reduce(T identity, BinaryOperator<T> accumulator)
3.1 Math operation with Stream elements providing initial value
- We are going to perform Math operations like sum, subtract, multiply and divide using reduce() method by providing initial/start value
- We can use either Lambda Expression or Method Reference for these Math operations
package net.bench.resources.stream.reduce.example;
import java.util.Arrays;
public class MathOperationUsingReduceAccumulator {
public static void main(String[] args) {
// primitive int[] array
int[] intArray = {
17, 23, 29, 31, 37, 41, 43, 47, 53, 58
};
// 1.1 reduce() - sum using Method Reference
int sum = Arrays
.stream(intArray)
.reduce(0, Integer::sum); // initial value 0
System.out.println("Stream.reduce() - "
+ "Sum with initial value 0 = "
+ sum);
// 1.2 reduce() - subtract using Lambda Expression
int subtract = Arrays
.stream(intArray)
.reduce(0, (m, n) -> m - n); // initial value 0
System.out.println("Stream.reduce() - "
+ "Subtract with initial value 0 = "
+ subtract);
// 1.3 reduce() - multiply using Method Reference
int multiply = Arrays
.stream(intArray) // initial value 0
.reduce(0, (m, n) -> m * n); // 0 multiply by whatever is 0
System.out.println("Stream.reduce() - "
+ "Multiply with initial value 0 = "
+ multiply);
// 1.4 reduce() - division using Lambda Expression
int division = Arrays
.stream(intArray) // initial value 0
.reduce(0, (m, n) -> m / n); // 0 divide by whatever is 0
System.out.println("Stream.reduce() - "
+ "Division with initial value 0 = "
+ division);
}
}
Output:
Stream.reduce() - Sum with initial value 0 = 379
Stream.reduce() - Subtract with initial value 0 = -379
Stream.reduce() - Multiply with initial value 0 = 0
Stream.reduce() - Division with initial value 0 = 0
3.2 String concatenation of Stream elements providing default value
- In this example, we are going to perform String concatenation operations using reduce() method by providing default value
- We can use either Lambda Expression or Method Reference
package net.bench.resources.stream.reduce.example;
import java.util.Arrays;
public class StringOperationUsingReduceAccumulator {
public static void main(String[] args) {
// initial value
String defaultValue = null;
// String[] array
String[] strArray = {
"is",
"a",
"Beautiful",
"destination"
};
// 1.1 default value assigned
defaultValue = "India";
// 1.2 Stream.reduce() - string concatenation using lambda
String strConcatIndia = Arrays
.stream(strArray)
.reduce(defaultValue, (str1, str2) -> str1 + " " + str2);
System.out.println("String Concatention 1 = " + strConcatIndia);
// 2.1 default value assigned
defaultValue = "Dubai";
// 2.2 Stream.reduce() - string concatenation using lambda
String strConcatDubai = Arrays
.stream(strArray)
.reduce(defaultValue, (str1, str2) -> str1 + " " + str2);
System.out.println("String Concatention 2 = " + strConcatDubai);
}
}
Output:
String Concatention 1 = India is a Beautiful destination
String Concatention 2 = Dubai is a Beautiful destination
3.3 Returns default value when Stream is empty
- When Stream is empty, then default/initial value is used for that particular operation using 2nd variant of reduce() method
- In the below illustration, we have used 3 different examples to show reduction when Stream is empty
- First, we are doing summation when Stream is empty and starting value is 1, so final sum is 1
- Second, we are doing multiplication when Stream is empty providing initial value 1, so final result of multiplication is 1
- Third, we are doing String concatenation when Stream is empty with default value ‘Australia’ and final string concatenation result is “Australia”
- Note: nowhere we have faced any exception when we have provided default/initial/starting value in the reduce() method
package net.bench.resources.stream.reduce.example;
import java.util.Arrays;
public class StreamIsEmpty {
public static void main(String[] args) {
// 1. empty primitive int array
int[] intArrayEmpty = {};
// 1.1 reduce() - sum using Method Reference
int sum = Arrays
.stream(intArrayEmpty)
.reduce(1, Integer::sum); // initial value 1
// 1.2 print to console
System.out.println("Stream.reduce() - Sum on empty "
+ "array with initial value 1 = "
+ sum);
// 2.1 reduce() - multiply using Method Reference
int multiply2 = Arrays
.stream(intArrayEmpty)
.reduce(1, (m, n) -> m * n); // initial value 1
// 2.2 print to console
System.out.println("Stream.reduce() - Multiply on empty "
+ "array with initial value 1 = "
+ multiply2);
// 3. empty String array
String[] strArrayEmpty = {};
// 3.1 default/initial value
String defaultValue = "Australia";
// 3.2 Stream.reduce() - string concatenation using lambda
String result3 = Arrays
.stream(strArrayEmpty)
.reduce(defaultValue, (str1, str2) -> str1 + " " + str2);
// 3.3 print to console
System.out.println("String concatenation with "
+ "initial value 'Australia' = "
+ result3);
}
}
Output:
Stream.reduce() - Sum on empty array with initial value 1 = 1
Stream.reduce() - Multiply on empty array with initial value 1 = 1
String concatenation with initial value 'Australia' = Australia
3.4 Map and Reduce 1 – to find average Age of a class
- A list contains 7 Student information with attributes like rollNo, name, and their age
- Extract age from Student information using Stream’s map() method
- And then we are going to reduce to get average age of class using Stream’s average() method which is one form of reduce() method
Student.java
package net.bench.resources.stream.reduce.example;
public class Student {
// member variables
private int rollNumber;
private String name;
private int age;
// constructors
// getters & setters
// toString()
}
MapReduceOnStudentAverage.java
package net.bench.resources.stream.reduce.example;
import java.util.Arrays;
import java.util.List;
public class MapReduceOnStudentAverage {
public static void main(String[] args) {
// list of students
List<Student> students = Arrays.asList(
new Student(1, "Viraj", 17),
new Student(2, "Krishnanand", 18),
new Student(3, "Rishi", 19),
new Student(4, "Suresh", 23),
new Student(5, "Aditya", 22),
new Student(6, "Rafat", 24),
new Student(7, "Mandar", 21)
);
// print original Student list
System.out.println("Original Student list :- \n");
students.stream().forEach(System.out::println);
// Stream.reduce() - to find Average age of class
double averageAge = students
.stream()
.mapToInt(student -> student.getAge()) // map
.average() // reduce
.getAsDouble(); // return value as double
System.out.format("\nThe average Age of class is = %.3f", averageAge);
}
}
Output:
Original Student list :-
Student [rollNumber=1, name=Viraj, age=17]
Student [rollNumber=2, name=Krishnanand, age=18]
Student [rollNumber=3, name=Rishi, age=19]
Student [rollNumber=4, name=Suresh, age=23]
Student [rollNumber=5, name=Aditya, age=22]
Student [rollNumber=6, name=Rafat, age=24]
Student [rollNumber=7, name=Mandar, age=21]
The average Age of class is = 20.571
3.5 Map and Reduce 2 – to find total amount of Grocery shopping
- A list contains Product information of 4 grocery items, each with attributes like id, name, quantity and price
- We are going to calculate amount of each items purchased by multiplying quantity with its price using Stream’s map() method
- Finally, we are finding total amount of all items purchased using Stream’s reduce() method (via Method reference Double::sum)
Product.java
package net.bench.resources.stream.reduce.example;
public class Product {
// member variables
private int productId;
private String productName;
private int quantity;
private double price;
// 4 arg parameterized constructor
// getters & setters
// toString()
}
MapReduceOnStudentAverage.java
package net.bench.resources.stream.reduce.example;
import java.util.ArrayList;
import java.util.List;
public class MapReduceOnGroceryTotalPrice {
public static void main(String[] args) {
// list of products
List<Product> products = new ArrayList<>();
// add products to cart
products.add(new Product(101, "Lentil", 4, 16.52)); // 4 lentil packets
products.add(new Product(102, "Wheat", 2, 33.31)); // 2 wheat packets
products.add(new Product(103, "Rice", 2, 55.69)); // 2 rice packets
products.add(new Product(104, "Oil", 3, 165.27)); // 3 oil packets
// print Product list
System.out.println("Grocery shopping list :- \n");
products.stream().forEach(System.out::println);
// Stream.reduce() - to find total amount of shopping
double totalAmount = products
.stream()
.map(product -> product.getPrice() * product.getQuantity()) // map
.reduce(Double.MIN_VALUE, Double::sum); // reduce
// total amount of shopping
System.out.format("\nThe total shopping amount is = %.2f",
totalAmount);
}
}
Output:
Grocery shopping list :-
Product [productId=101, productName=Lentil, quantity=4, price=16.52]
Product [productId=102, productName=Wheat, quantity=2, price=33.31]
Product [productId=103, productName=Rice, quantity=2, price=55.69]
Product [productId=104, productName=Oil, quantity=3, price=165.27]
The total shopping amount is = 739.89
4. Stream reduce() method with Identity, accumulator and combiner :
- This is the third variant of the reduce() method which takes three arguments
1. Identity as initial/default value
2. BiFunction as accumulartor
3. BinaryOperator as combiner - This is mainly used in Parallel Stream
- This is very much similar to 2nd variant except a combiner to combine the result when working in Parallel Stream
- In Parallel Stream, streams are sub-divided into sub-streams and further computation/aggregate function are executed to achieve the target, so to combine the result we need combiner
- Identity value will be used as a starting value for combiner
- Method signature :- <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
4.1 Math operation in Parallel Stream
- First, we are going to calculate sum of all Stream elements with starting value of 0
- Second, we are going to multiply all Stream elements with starting value of 1
- We can use either Lambda Expression or Method Reference for these Math operations
package net.bench.resources.stream.reduce.example;
import java.util.Arrays;
import java.util.List;
public class ReduceWithParallelStream {
public static void main(String[] args) {
// List of Integers
List<Integer> numbers = Arrays.asList(
10, 20, 30, 40, 50
);
// 1.1 reduce() - sum using combiner
int sum = numbers
.parallelStream() // parallel stream
.reduce(0, // initial value 0
(x, y) -> x + y, // accumulator
Integer::sum // combiner
);
System.out.println("Parallel Stream reduce() - "
+ "Sum with initial value 0 = "
+ sum);
// 1.3 reduce() - multiply using combiner
int multiply = numbers
.parallelStream() // parallel stream
.reduce(1, // initial value 1
(m, n) -> m * n, // accumulator
(A, B) -> A * B // combiner
);
System.out.println("Parallel Stream reduce() - "
+ "Multiply with initial value 1 = "
+ multiply);
}
}
Output:
Parallel Stream reduce() - Sum with initial value 0 = 150
Parallel Stream reduce() - Multiply with initial value 1 = 12000000
5. Filter, Map and Reduce :
- In this example, we are going to filter based on some criteria and then extract some value with map and finally reducing to get final desired value
5.1 Get total/average salary and count of employees working in IT department
- A list of employees is defined with attributes like id, name, department and their salary
- Filter – we are filtering to get employees of IT department
- Map – extracting salary information from employee list after filtering
- Reduce 1 – finding total salary of all IT department employees
- Reduce 2 – finding count of IT department employees
- Reduce 3 – finding average salary of IT department employees and returning double value to get accurate result with fractional portion
Employee.java
package net.bench.resources.stream.reduce.example;
public class Employee {
// member variables
private int id;
private String name;
private String department;
private long salary;
// 4 arg parameterized constructor
// getters & setters
// toString()
}
FilterMapReduceOnEmployeeSalaryAverage.java
package net.bench.resources.stream.reduce.example;
import java.util.Arrays;
import java.util.List;
public class FilterMapReduceOnEmployeeSalaryAverage {
public static void main(String[] args) {
// 1. list of employees
List<Employee> employees = Arrays.asList(
new Employee(101, "Krish", "IT", 87000),
new Employee(102, "Viraj", "Admin", 62000),
new Employee(103, "Suresh", "IT", 76000),
new Employee(104, "Aditya", "IT", 81000),
new Employee(105, "Rishi", "HR", 68000)
);
// 1.2 print original Student list
System.out.println("Employees list :- \n");
employees.stream().forEach(System.out::println);
// 2. get TOTAL salary of IT employees
long totalSalaryOfItEmp = employees
.stream()
.filter(employee -> employee.getDepartment()
.equalsIgnoreCase("IT")) // filter
.mapToLong(employee -> employee.getSalary()) // map
.sum(); // reduce
// 2.1 print to console
System.out.println("\nThe Total salary of employees"
+ " working in IT department is = "
+ totalSalaryOfItEmp);
// 3. get COUNT of IT employees
long countOfItEmp = employees
.stream()
.filter(employee -> employee.getDepartment()
.equalsIgnoreCase("IT")) // filter
.count(); // reduce
// 3.1 print to console
System.out.println("\nCount of employees"
+ " working in IT department is = "
+ countOfItEmp);
// 4. get AVERAGE salary of IT employees
double average = employees
.stream()
.filter(employee -> employee.getDepartment()
.equalsIgnoreCase("IT")) // filter
.mapToLong(employee -> employee.getSalary()) // map
.average() // reduce
.getAsDouble(); // return double
// 4.1 print to console
System.out.println("\nThe average salary of employees"
+ " working in IT department is = "
+ average);
}
}
Output:
Employees list :-
Employee [id=101, name=Krish, department=IT, salary=87000]
Employee [id=102, name=Viraj, department=Admin, salary=62000]
Employee [id=103, name=Suresh, department=IT, salary=76000]
Employee [id=104, name=Aditya, department=IT, salary=81000]
Employee [id=105, name=Rishi, department=HR, salary=68000]
The Total salary of employees working in IT department is = 244000
Count of employees working in IT department is = 3
The average salary of employees working in IT department is = 81333.33333333333
References:
- https://docs.oracle.com/javase/tutorial/collections/streams/reduction.html
- https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
- https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html
- https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html
Happy Coding !!
Happy Learning !!