Definition
- toMap(keyMapper, valueMapper, mergeFunction) builds a map; the merge function resolves same-department collisions.
- BinaryOperator.maxBy(comparator) turns a Comparator into a merge function returning the larger element.
- The value mapper
e -> e(identity) keeps the whole Employee object. - The result is
Mapwith no Optional wrapper. - Ties are resolved by whichever element the comparator considers not-less.
For each department at the National Bank of India, Priya wants to pin up the photo of the top earner. As she goes through the staff, whenever she meets another person from a department she already has, she keeps the one who earns more.
Understanding the Concepts
How do you find the highest-paid employee per department?
The key mapper Employee::getDepartment groups by department. The value mapper e -> e keeps the whole Employee object. Because many employees share a department, duplicates are expected, so you supply BinaryOperator.maxBy with a salary comparator as the merge function.
package com.bank.hr;
import java.util.*;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;
public class HighestPaidPerDept {
public static void main(String[] args) {
List<Employee> employees = List.of(
new Employee("Rajesh", "Loans", 75000.0),
new Employee("Priya", "Loans", 82000.0),
new Employee("Amit", "IT", 68000.0),
new Employee("Kavita", "IT", 91000.0),
new Employee("Neha", "Finance", 78000.0)
);
Map<String, Employee> topEarners = employees.stream()
.collect(Collectors.toMap(
Employee::getDepartment,
e -> e,
BinaryOperator.maxBy(
Comparator.comparingDouble(
Employee::getSalaryAsDouble))));
topEarners.forEach((dept, emp) ->
System.out.println(dept + ": " + emp.getName()
+ " (" + emp.getSalaryAsDouble() + ")"));
}
}
Loans: Priya (82000.0)
IT: Kavita (91000.0)
Finance: Neha (78000.0)
How is this different from groupingBy with maxBy?
The groupingBy approach with maxBy produces Map, which wraps each value in an Optional. The toMap approach produces Map directly.
| Approach | Result type | Optional wrapper? |
|---|---|---|
| toMap + BinaryOperator.maxBy | Map
| groupingBy + Collectors.maxBy | Map
// groupingBy + maxBy: produces Optional wrapper
Map<String, Optional<Employee>> withOptional = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.maxBy(Comparator.comparingDouble(
Employee::getSalaryAsDouble))));
// toMap + BinaryOperator.maxBy: no Optional
Map<String, Employee> withoutOptional = employees.stream()
.collect(Collectors.toMap(
Employee::getDepartment,
e -> e,
BinaryOperator.maxBy(Comparator.comparingDouble(
Employee::getSalaryAsDouble))));
The toMap form is often more convenient because consumers do not need to unwrap an Optional for each department.
What is BinaryOperator.maxBy?
BinaryOperator.maxBy(comparator) is a convenience factory that turns any Comparator into a BinaryOperator returning the larger of two elements. This avoids writing a verbose ternary lambda.
// Verbose lambda
BinaryOperator<Employee> verbose =
(a, b) -> a.getSalaryAsDouble() >= b.getSalaryAsDouble() ? a : b;
// Clean with maxBy
BinaryOperator<Employee> clean =
BinaryOperator.maxBy(
Comparator.comparingDouble(Employee::getSalaryAsDouble));
How do you handle ties?
If two employees in the same department have the same salary, maxBy returns whichever element the comparator considers not-less (the first in encounter order for equal elements). For deterministic tie-breaking, extend the comparator with a secondary key.
BinaryOperator<Employee> deterministicMax =
BinaryOperator.maxBy(
Comparator.comparingDouble(Employee::getSalaryAsDouble)
.thenComparing(Employee::getName));
What are common mistakes?
Mistake: using Function.identity() but getting a compile error.
// This might cause type inference issues:
Collectors.toMap(
Employee::getDepartment,
Function.identity(), // sometimes the compiler cannot infer
BinaryOperator.maxBy(comparator));
// Safer: explicit lambda
Collectors.toMap(
Employee::getDepartment,
e -> e, // always works
BinaryOperator.maxBy(comparator));
When should you use toMap vs groupingBy?
Use toMap when you want exactly one value per key (the top earner, the latest record, the maximum). Use groupingBy when you want a collection per key (all employees per department).
What should a senior engineer know?
- BinaryOperator.maxBy also has a companion minBy for finding the minimum.
- The toMap form assumes at least one element per group since there is no empty-key scenario. If a department might legitimately have zero employees after filtering, groupingBy with maxBy and its Optional is safer.
- For sorted output, supply a TreeMap in the four-argument toMap.
What are common interview follow-up questions?
- "How do you find the lowest-paid employee per department?" Replace maxBy with BinaryOperator.minBy.
- "How do you get the top 3 per department?" Use groupingBy with a collectingAndThen that sorts and limits.
- "How do you find the highest-paid across the whole company?" Use stream().max(comparing(salary)).
Visual Overview
Stream<Employee>
[Rajesh:Loans:75K, Priya:Loans:82K, Amit:IT:68K, Kavita:IT:91K, Neha:Finance:78K]
│
│ toMap(dept, e->e, maxBy(salary))
▼
Processing:
Rajesh → Loans:Rajesh(75K) (insert)
Priya → Loans:Priya(82K) (merge: max(75K,82K) → Priya)
Amit → IT:Amit(68K) (insert)
Kavita → IT:Kavita(91K) (merge: max(68K,91K) → Kavita)
Neha → Finance:Neha(78K) (insert)
│
▼
Map<String, Employee>
┌──────────────────────────────┐
│ Loans → Priya (82000.0) │
│ IT → Kavita (91000.0) │
│ Finance → Neha (78000.0) │
└──────────────────────────────┘
Key Takeaway
To find the highest-paid employee per department, collect with toMap using the department as the key, the whole employee as the value, and BinaryOperator.maxBy as the merge function. When two employees collide on the same department, maxBy returns the one with the higher salary.
This form produces a plain Map
BinaryOperator.maxBy is a convenience adapter that turns any Comparator into a merge function. For deterministic tie-breaking, extend the comparator with a secondary key like employee name.
Remember one sentence: toMap with BinaryOperator.maxBy gives one highest-paid employee per department with no Optional wrapper.
---