Definition
reduce(identity, accumulator, combiner)folds all elements into a single value.- Identity:
BigDecimal.ZEROis the starting value and the result when the stream is empty. - Accumulator:
(sum, e) -> sum.add(e.getSalary())folds each employee into the running total. - Combiner:
BigDecimal::addmerges partial totals for parallel execution. - BigDecimal: exact arithmetic for money; double would accumulate rounding error.
Priya at the National Bank of India wants the grand total of everyone's salary. She starts with a running total of zero, then walks down the staff list adding each salary. At the end the running total is the answer.
Understanding the Concepts
What does reduce do?
reduce turns many values into one. The identity is the starting value, the accumulator folds each element in, and the combiner merges partial results when the stream runs in parallel.
How do you total salaries with reduce?
package com.bank.hr;
import java.math.BigDecimal;
import java.util.List;
public class TotalSalary {
public static void main(String[] args) {
List<Employee> staff = List.of(
new Employee("Rajesh", "Loans", new BigDecimal("95000")),
new Employee("Priya", "IT", new BigDecimal("120000")),
new Employee("Amit", "Finance", new BigDecimal("85000")),
new Employee("Kavita", "Loans", new BigDecimal("78000")));
BigDecimal total = staff.stream()
.reduce(
BigDecimal.ZERO,
(sum, e) -> sum.add(e.getSalary()),
BigDecimal::add);
System.out.println("Total salary: Rs. " + total.toPlainString());
}
}
Total salary: Rs. 378000
Why use the three-argument reduce?
Because an Employee is not itself a BigDecimal, you need the three-argument form. The two-argument form requires both the identity and elements to be the same type.
| Form | Signature | When to use | |---|---|---| | Two-argument | reduce(identity, BinaryOperator<T>) | Same type: e.g. sum of ints | | Three-argument | reduce(identity, BiFunction<U,T,U>, BinaryOperator<U>) | Different types: Employee → BigDecimal |
Why BigDecimal and not double?
// WRONG: double accumulates rounding error
double total = staff.stream()
.mapToDouble(e -> e.getSalary().doubleValue())
.sum();
// May not be exactly 378000 due to floating-point representation
// CORRECT: BigDecimal.add is exact
BigDecimal total = staff.stream()
.reduce(BigDecimal.ZERO,
(sum, e) -> sum.add(e.getSalary()),
BigDecimal::add);
// Exactly 378000
What is the combiner for?
The combiner merges two partial BigDecimal totals when the stream runs in parallel. In a sequential stream the combiner is effectively unused, but supplying it keeps the code correct if someone later calls parallel().
What are common mistakes?
Mistake: using the wrong identity for addition.
// WRONG: identity 100 inflates every result by 100
BigDecimal wrong = staff.stream()
.reduce(new BigDecimal("100"),
(sum, e) -> sum.add(e.getSalary()),
BigDecimal::add);
// 378100, not 378000!
// CORRECT: identity for addition is ZERO
BigDecimal correct = staff.stream()
.reduce(BigDecimal.ZERO,
(sum, e) -> sum.add(e.getSalary()),
BigDecimal::add);
What about an empty employee list?
With the identity form, the result for an empty stream is the identity itself: BigDecimal.ZERO. This is correct: the total salary when no one is on the payroll is zero.
What is the simpler alternative for a plain sum?
For a simple numeric sum, map to BigDecimal and reduce with the two-argument form is cleaner.
// Two-step: map then reduce
BigDecimal total = staff.stream()
.map(Employee::getSalary)
.reduce(BigDecimal.ZERO, BigDecimal::add);
Or with Collectors.reducing:
import java.util.stream.Collectors;
BigDecimal total = staff.stream()
.map(Employee::getSalary)
.collect(Collectors.reducing(BigDecimal.ZERO, BigDecimal::add));
What should a senior engineer know?
- The identity must be a true identity for the operation:
ZERO + x == x. - The accumulator must be associative for parallel correctness.
- For a plain numeric sum,
mapToDouble(...).sum()or the Collectors approach is simpler; reduce is for demonstrating the fold. - In production, consider
Collectors.reducingas a downstream ofgroupingByfor per-department totals.
What are common interview follow-up questions?
- "Why not use double for money?" double is imprecise.
- "What is the identity for multiplication?" 1 (BigDecimal.ONE).
- "What happens with an empty stream?" The identity is returned.
Visual Overview
reduce(ZERO, (sum, e) -> sum.add(e.salary), BigDecimal::add)
Step 1: ZERO + 95000 = 95000
Step 2: 95000 + 120000 = 215000
Step 3: 215000 + 85000 = 300000
Step 4: 300000 + 78000 = 378000
Result: BigDecimal("378000")
Empty stream → returns identity: BigDecimal.ZERO
Key Takeaway
To total salaries with reduce I use the three-argument form because an Employee is not itself a number. The identity is BigDecimal.ZERO, the accumulator adds each salary to the running total, and the combiner merges partial totals for parallel execution.
I always use BigDecimal for money, never double, because double accumulates rounding error over many values. For an empty stream the identity form returns ZERO, which is the correct total when no one is on the payroll.
In my banking project at the National Bank of India this computed the total monthly salary outflow. For a plain numeric sum, the two-step approach of map(getSalary).reduce(ZERO, BigDecimal::add) is simpler, but the three-argument form demonstrates the fold.
Remember one sentence: reduce(ZERO, (sum, e) -> sum.add(salary), BigDecimal::add) folds all salaries into one exact BigDecimal total.
---