Definition
- Comparator.comparing(keyExtractor): creates a Comparator from a function that extracts the sort key
- comparingInt/Long/Double: primitive-specific versions that avoid autoboxing overhead
- thenComparing(keyExtractor): adds a secondary sort applied when the primary comparison returns 0
- reversed(): reverses the entire Comparator chain or a single level
- nullsFirst(comparator) / nullsLast(comparator): wraps a Comparator to handle null values by placing them first or last
The bank manager writes a sorting rule: "Line customers by account balance (highest first). If two have the same balance, sort by name alphabetically. If names also match, sort by account opening date." Each level is a thenComparing call in the chain.
Understanding the Concepts
How do you implement a Comparator to sort by multiple fields?
Java 8 Comparator factory methods make multi-field sorting clean and readable.
package com.bank.service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.*;
public class MultiFieldSortDemo {
record Employee(String name, int age, String department, BigDecimal salary) {}
public static void main(String[] args) {
List<Employee> staff = new ArrayList<>(List.of(
new Employee("Rajesh", 35, "IT", new BigDecimal("120000")),
new Employee("Priya", 30, "Finance", new BigDecimal("95000")),
new Employee("Amit", 30, "IT", new BigDecimal("110000")),
new Employee("Sneha", 28, "Finance", new BigDecimal("95000")),
new Employee("Vikram", 35, "Operations", new BigDecimal("105000"))
));
// Sort by age, then by name
staff.sort(Comparator.comparingInt(Employee::age)
.thenComparing(Employee::name));
System.out.println("By age, then name:");
staff.forEach(e -> System.out.println(" " + e.name() + " (" + e.age() + ")"));
// Sort by salary descending, then by name ascending
staff.sort(Comparator.comparing(Employee::salary).reversed()
.thenComparing(Employee::name));
System.out.println("\nBy salary desc, then name:");
staff.forEach(e -> System.out.println(" " + e.name() + ": " + e.salary()));
// Sort by department, then by salary descending within each department
staff.sort(Comparator.comparing(Employee::department)
.thenComparing(Employee::salary, Comparator.reverseOrder()));
System.out.println("\nBy department, then salary desc:");
staff.forEach(e -> System.out.println(" " + e.department() + " | " + e.name() + ": " + e.salary()));
}
}
By age, then name:
Sneha (28)
Amit (30)
Priya (30)
Rajesh (35)
Vikram (35)
By salary desc, then name:
Rajesh: 120000
Amit: 110000
Vikram: 105000
Priya: 95000
Sneha: 95000
By department, then salary desc:
Finance | Priya: 95000
Finance | Sneha: 95000
IT | Rajesh: 120000
IT | Amit: 110000
Operations | Vikram: 105000
How do you handle nulls in sorting?
package com.bank.demo;
import java.util.*;
public class NullSafeSortDemo {
record Account(String holder, Integer priority) {}
public static void main(String[] args) {
List<Account> accounts = new ArrayList<>(List.of(
new Account("Rajesh", 1),
new Account(null, 2), // null holder
new Account("Priya", null), // null priority
new Account("Amit", 3)
));
// nullsFirst: null holders sort before non-null
accounts.sort(Comparator.comparing(
Account::holder,
Comparator.nullsFirst(Comparator.naturalOrder())
));
System.out.println("Null holders first:");
accounts.forEach(a -> System.out.println(" " + a.holder()));
}
}
Null holders first:
null
Amit
Priya
Rajesh
What are the old-style vs new-style patterns?
// OLD STYLE (pre-Java 8): anonymous class
Comparator<Employee> oldStyle = new Comparator<Employee>() {
@Override
public int compare(Employee a, Employee b) {
int cmp = Integer.compare(a.age(), b.age());
if (cmp != 0) return cmp;
return a.name().compareTo(b.name());
}
};
// NEW STYLE (Java 8+): factory methods + chaining
Comparator<Employee> newStyle = Comparator.comparingInt(Employee::age)
.thenComparing(Employee::name);
// Both produce the same result, but the new style is:
// - More readable (reads like English)
// - Null-safe (with nullsFirst/nullsLast)
// - Composable (thenComparing, reversed)
// - Less error-prone (no manual Integer.compare / subtraction)
What are common mistakes?
Mistake: Using subtraction instead of Integer.compare
// WRONG: integer overflow for large values
(a, b) -> a.age() - b.age() // if age is Integer.MIN_VALUE and b is positive: overflow!
// CORRECT: use Integer.compare
(a, b) -> Integer.compare(a.age(), b.age())
// BEST: use comparingInt
Comparator.comparingInt(Employee::age)
What should a senior engineer know?
- comparingInt avoids autoboxing. Comparator.comparing(Employee::getAge) boxes int to Integer for comparison. Comparator.comparingInt(Employee::getAge) uses primitive comparison directly – faster for large sorts.
- Comparator is a functional interface. You can use it as a lambda: (a, b) -> a.name().compareTo(b.name()). But factory methods are preferred for readability and safety.
- Stable sorting. Java's sort (TimSort) is stable: equal elements keep their relative order. This means sorting by department first, then sorting by name, gives the same result as a single sort by department-then-name.
What are common interview follow-up questions?
- "How do you sort by multiple fields?" – Comparator.comparing().thenComparing().
- "How do you sort in descending order?" – .reversed().
- "How do you handle nulls in sorting?" – nullsFirst() or nullsLast().
- "What is the difference between comparing() and comparingInt()?" – comparingInt avoids autoboxing.
Visual Overview
Multi-field Comparator chaining
Comparator.comparingInt(Employee::age) ← primary: age asc
.thenComparing(Employee::name) ← secondary: name asc
.thenComparing(Employee::salary, ← tertiary: salary desc
Comparator.reverseOrder())
Input: Rajesh(35), Priya(30), Amit(30)
Step 1: age: Priya(30), Amit(30), Rajesh(35)
Step 2: tie on age=30: Amit, Priya (alphabetical)
Result: Amit(30), Priya(30), Rajesh(35)
Key Takeaway
Java 8 Comparator factory methods make multi-field sorting clean and composable. Use comparing() for the primary sort, thenComparing() for tie-breaking, reversed() for descending order, and nullsFirst()/nullsLast() for null safety.
In the National Bank of India's HR reporting system, Rajesh sorts employees by department, then by salary descending within each department, using a single chained Comparator. The factory methods are more readable, more type-safe, and less error-prone than hand-written compare blocks.
Remember one sentence: Comparator.comparing().thenComparing().reversed() gives you multi-field, null-safe sorting in one readable chain.
---