Definition
- Single-argument
reduce(accumulator)folds pairwise with no identity, returningOptional. - Optional: correctly represents the empty case where there are no employees.
- BigDecimal comparison: use
compareTo, not>or>=, because BigDecimal is an object. - Tie-breaking:
>=keeps the earlier of two equal employees, making the result deterministic. - Idiomatic alternative:
stream.max(Comparator.comparing(Employee::getSalary))is cleaner for production.
Priya walks down the staff list holding a current highest earner. For each colleague she checks: does this person earn more? If yes, she swaps. At the end, whoever she is holding is the top earner.
Understanding the Concepts
How do you find the max-salary employee with reduce?
package com.bank.hr;
import java.math.BigDecimal;
import java.util.*;
public class MaxSalaryReduce {
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")));
Optional<Employee> topEarner = staff.stream()
.reduce((a, b) -> a.getSalary().compareTo(b.getSalary()) >= 0 ? a : b);
topEarner.ifPresent(e -> System.out.printf("Top earner: %s (Rs. %s)%n",
e.getName(), e.getSalary().toPlainString()));
}
}
Top earner: Priya (Rs. 120000)
Why does this return Optional?
The single-argument reduce has no identity value. If the stream is empty, there is no first element and therefore no result, so it returns Optional.empty(). The caller must handle the empty case.
// Handle the empty case
Employee top = topEarner.orElseThrow(
() -> new IllegalStateException("No employees"));
How is reduce different from max?
| Method | Code | Returns | |---|---|---| | reduce | `reduce((a,b) -> compare and keep larger)` | Optional<Employee> | | max | `max(Comparator.comparing(Employee::getSalary))` | Optional<Employee> |
Both return Optional and produce the same result. max is cleaner for production; reduce demonstrates the underlying fold.
// Idiomatic alternative
Optional<Employee> topEarner = staff.stream()
.max(Comparator.comparing(Employee::getSalary));
Why use compareTo instead of >= for BigDecimal?
BigDecimal is an object, not a primitive. The >= operator does not exist for objects. Use compareTo, which returns a negative, zero, or positive int.
// WRONG: >= does not compile for BigDecimal
// a.getSalary() >= b.getSalary() // compilation error!
// CORRECT: compareTo
a.getSalary().compareTo(b.getSalary()) >= 0 ? a : b
What are common mistakes?
Mistake: calling get() on the Optional without checking, crashing on an empty stream.
// WRONG: NoSuchElementException on empty stream
Employee top = staff.stream()
.reduce((a, b) -> a.getSalary().compareTo(b.getSalary()) >= 0 ? a : b)
.get(); // throws if staff is empty!
// CORRECT: handle the empty case
Employee top = staff.stream()
.reduce((a, b) -> a.getSalary().compareTo(b.getSalary()) >= 0 ? a : b)
.orElseThrow(() -> new IllegalStateException("No employees"));
What about deterministic tie-breaking?
Using >= 0 keeps the earlier of two equal-salary employees (the one already held). Using > 0 would keep the later one. >= is more intuitive: first occurrence wins.
What about finding the minimum instead?
Flip the comparison: keep the employee with the smaller salary.
Optional<Employee> lowest = staff.stream()
.reduce((a, b) -> a.getSalary().compareTo(b.getSalary()) <= 0 ? a : b);
What should a senior engineer know?
maxandminare both syntactic sugar over reduce with a comparator.- The reduce form is useful in interviews to show you understand the fold.
- For production, use
stream.max(Comparator.comparing(Employee::getSalary))for clarity. - The accumulator must be associative for correct parallel execution.
What are common interview follow-up questions?
- "How is reduce different from max?" max is syntactic sugar; reduce is the underlying fold.
- "What happens with an empty stream?" Optional.empty() is returned.
- "How do you break ties?" The comparison operator direction determines which tie-breaking element is kept.
Visual Overview
reduce((a, b) -> a.salary >= b.salary ? a : b)
Step 1: Rajesh(95000) vs Priya(120000)
95000 < 120000 → keep Priya
Step 2: Priya(120000) vs Amit(85000)
120000 > 85000 → keep Priya
Step 3: Priya(120000) vs Kavita(78000)
120000 > 78000 → keep Priya
Result: Optional[Priya(120000)]
Empty stream → Optional.empty()
Key Takeaway
Finding the maximum is a reduction. The single-argument reduce takes a binary operator that keeps the employee with the higher salary at each step, and because it has no identity, it returns an Optional. This is correct: an empty stream has no maximum.
I compare BigDecimal salaries with compareTo because operators like >= do not exist for objects. Using >= 0 keeps the earlier employee on ties, making the result deterministic.
In day-to-day code I use stream.max(Comparator.comparing(Employee::getSalary)), which is cleaner, but the reduce form demonstrates that max is fundamentally a fold. In my banking project at the National Bank of India this powered an executive compensation review.
Remember one sentence: reduce((a, b) -> a.salary.compareTo(b.salary) >= 0 ? a : b) folds to the top earner, returning Optional to handle the empty case.
---