Back to Collectors
Q16Question 16 of 21

Collect employees into a TreeSet<Employee> sorted by salary

Key Concepts
  • toCollection(() -> new TreeSet<>(comparator)) collects into a TreeSet ordered by the comparator
  • Elements are inserted in sorted position and the set stays sorted during iteration
  • Critical trap: TreeSet determines equality by the comparator, not by equals()
  • A salary-only comparator treats employees with the same salary as duplicates, silently dropping one
  • Add a tie-breaker like thenComparing(id) to keep distinct employees while sorting by salary
  • Use toCollection over toSet when you need control over the concrete set type and ordering

Definition

`toCollection(() -> new TreeSet<>(comparing(salary)))` collects employees into a salary-sorted set, but beware: TreeSet uses the comparator for equality.
  • toCollection(supplier) lets you choose the exact collection to collect into.
  • TreeSet with comparator keeps elements in sorted order by the comparator.
  • Comparator-based equality: TreeSet considers two elements equal if comparator.compare(a, b) == 0.
  • Salary-only comparator silently drops employees with the same salary.
  • Fix: add a tie-breaker like thenComparing(Employee::id) to retain distinct employees.

At the National Bank of India, Priya files employees into a self-sorting drawer ordered by salary. But if two employees earn the same amount, the drawer thinks they are the same person and keeps only one. She must add a secondary sort (by ID) so both are kept.

Understanding the Concepts

How does TreeSet with a comparator work?

TreeSet uses a comparator (or natural ordering) to both sort and determine uniqueness. When you supply a salary comparator, the set sorts employees by salary and treats two employees as equal if their salaries compare as equal. This means employees with the same salary are silently deduplicated.

How do you implement it?

JAVA
package com.bank.hr;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

public class TreeSetCollector {

    record Employee(String id, String name, BigDecimal salary) {}

    public static void main(String[] args) {
        List<Employee> staff = List.of(
            new Employee("SAV2001", "Rajesh",  new BigDecimal("1800000")),
            new Employee("SAV2002", "Priya",   new BigDecimal("1000000")),
            new Employee("SAV2003", "Kavita",  new BigDecimal("1000000")),
            new Employee("SAV2004", "Deepak",  new BigDecimal("1500000"))
        );

        // DANGEROUS: salary-only comparator drops Kavita (same salary as Priya)
        TreeSet<Employee> buggy = staff.stream()
            .collect(Collectors.toCollection(
                () -> new TreeSet<>(Comparator.comparing(Employee::salary))));
        System.out.println("Buggy count: " + buggy.size());  // 3, not 4!

        // CORRECT: add tie-breaker to keep distinct employees
        TreeSet<Employee> correct = staff.stream()
            .collect(Collectors.toCollection(
                () -> new TreeSet<>(
                    Comparator.comparing(Employee::salary)
                        .thenComparing(Employee::id))));
        System.out.println("Correct count: " + correct.size());  // 4

        correct.forEach(e ->
            System.out.println("  " + e.name() + ": Rs." + e.salary()));
    }
}
TEXT
Buggy count: 3
Correct count: 4
  Priya: Rs.1000000
  Kavita: Rs.1000000
  Deepak: Rs.1500000
  Rajesh: Rs.1800000

Why does the salary-only comparator drop employees?

TreeSet uses the comparator for equality: if comparator.compare(a, b) == 0, the elements are considered the same and one is discarded. With a salary-only comparator, Priya and Kavita both earn Rs.1,000,000, so compare(Priya, Kavita) == 0 and one is dropped.

How is TreeSet equality different from equals()?

| Aspect | TreeSet (comparator) | HashSet (equals/hashCode) |
|---|---|---|
| Equality check | comparator.compare(a, b) == 0 | a.equals(b) && a.hashCode() == b.hashCode() |
| Sorting | Yes (by comparator) | No (unordered) |
| Same-salary employees | Treated as duplicates (if no tie-breaker) | Distinct (different IDs) |

What are common mistakes?

Mistake: Using a salary-only comparator and silently losing data.

JAVA
// WRONG: same salary = same employee in TreeSet
new TreeSet<>(Comparator.comparing(Employee::salary))
// Priya and Kavita (both Rs.1M) → only one kept

// CORRECT: add a tie-breaker
new TreeSet<>(Comparator.comparing(Employee::salary)
    .thenComparing(Employee::id))
// Both Priya and Kavita kept, sorted by salary then by ID

When should you use toCollection vs toSet?

toSet() gives no control over the concrete type or ordering. toCollection(supplier) lets you choose exactly the collection type and comparator.

JAVA
// toSet(): HashSet, no order, no comparator
Set<Employee> hashSet = staff.stream().collect(Collectors.toSet());

// toCollection(): TreeSet, salary-sorted, with tie-breaker
TreeSet<Employee> treeSet = staff.stream()
    .collect(Collectors.toCollection(
        () -> new TreeSet<>(Comparator.comparing(Employee::salary)
            .thenComparing(Employee::id))));

What about using sorted().toList() as an alternative?

If you do not need deduplication and just want a sorted list, sorted(comparator).toList() is simpler and does not have the comparator-equality trap.

JAVA
List<Employee> sortedList = staff.stream()
    .sorted(Comparator.comparing(Employee::salary))
    .toList();
// All employees kept, sorted by salary

What should a senior engineer know?

The comparator-equality pitfall is a classic TreeSet trap. A senior engineer always adds a tie-breaker to the comparator when collecting into a TreeSet, ensuring that genuinely different elements are not silently dropped. When deduplication is not desired, using sorted().toList() avoids the trap entirely.

What are common interview follow-up questions?

  • "What happens if two employees have the same salary?" With salary-only comparator, one is dropped. Add a tie-breaker.
  • "How is TreeSet equality different from equals?" TreeSet uses the comparator, not equals.
  • "When should you use TreeSet vs sorted().toList()?" TreeSet when you want sorted deduplication; sorted list when you want all elements sorted.

Visual Overview

DIAGRAM
  Salary-only comparator (DANGEROUS):
  ┌──────────────────────────────────────────┐
  │  Insert Rajesh  (1.8M)  → [Rajesh]      │
  │  Insert Priya   (1.0M)  → [Priya, Raj]  │
  │  Insert Kavita  (1.0M)  → compare=0     │
  │                         → DROPPED!       │
  │  Insert Deepak  (1.5M)  → [Priya,Deep,Raj]│
  └──────────────────────────────────────────┘
  Result: 3 employees (Kavita lost!)

  With tie-breaker (CORRECT):
  ┌──────────────────────────────────────────┐
  │  Insert Kavita  (1.0M, SAV2003) → kept  │
  │  compare by salary: 0                    │
  │  compare by id: SAV2002 vs SAV2003 ≠ 0  │
  │  → both kept in sorted order             │
  └──────────────────────────────────────────┘
  Result: 4 employees (all present)

Key Takeaway

Collecting into a TreeSet with a salary comparator gives a salary-sorted set, but the critical trap is that TreeSet determines equality by the comparator, not by equals(). If two employees earn the same salary, the comparator returns 0, and the set silently drops one of them.

At the National Bank of India, Priya discovered this when a ranked roster was missing employees. The fix is to always add a tie-breaker to the comparator: comparing(salary).thenComparing(id) ensures that genuinely different employees are retained even when their salaries match.

Choosing toCollection over toSet gives control over both the concrete type and the ordering. When deduplication is not desired, sorted(comparator).toList() avoids the TreeSet equality trap entirely while still producing sorted output.

Remember one sentence: always add a tie-breaker to a TreeSet comparator because TreeSet uses the comparator for equality, silently dropping elements where compare returns 0.

---

Share
Use ← → arrow keys to navigate