What is Comparable and Comparator?
Collections.sort(list) — takes a List and optionally a Comparator. list.sort(comparator) — a default method from Java 8. For ArrayList, both are identical. Prefer list.sort() —...
🟢 Junior Level
Comparable — the object ITSELF knows how to compare itself. Comparator — an external “judge” compares two objects.
// Comparable: inside the class
class User implements Comparable<User> {
String name;
public int compareTo(User other) {
return this.name.compareTo(other.name); // By name
}
}
// Comparator: a separate "judge"
Comparator<User> byAge = (u1, u2) -> u1.age - u2.age;
// Usage:
Collections.sort(users); // Uses compareTo
users.sort(byAge); // Uses Comparator
Collections.sort(list) — takes a List and optionally a Comparator. list.sort(comparator) — a default method from Java 8. For ArrayList, both are identical. Prefer list.sort() — it’s shorter.
🟡 Middle Level
Comparable — natural ordering
// String, Integer, LocalDate implement Comparable
"apple".compareTo("banana"); // → negative
LocalDate.now().compareTo(LocalDate.MAX); // → negative
// Only ONE ordering per class
Comparator — multiple strategies
// Java 8+: declarative style
Comparator<User> comparator = Comparator
.comparing(User::getLastName) // By last name
.thenComparing(User::getFirstName) // Then by first name
.thenComparingInt(User::getAge) // Then by age
.reversed(); // In reverse order
// Handling null
Comparator<User> safe = Comparator
.comparing(User::getName, Comparator.nullsLast(String::compareTo));
Danger of subtraction
// ❌ Overflow!
return o1.id - o2.id; // Can exceed int bounds!
// ✅ Correct:
return Integer.compare(o1.id, o2.id);
return Long.compare(o1.id, o2.id);
For example: o1.id = -2_000_000_000, o2.id = 2_000_000_000. Result: -2_000_000_000 - 2_000_000_000 = -4_000_000_000 — outside int range (max 2_147_483_647). The result becomes a positive number, sorting is incorrect.
🔴 Senior Level
When NOT to use Comparable
- Need multiple sorting variants of the same class — use Comparator
- Class from a third-party library (cannot modify) — use Comparator
- Ordering depends on context/locale (string sorting for different languages) — Comparator
Consistency with equals
// TreeSet/TreeMap use compareTo, NOT equals!
// If compareTo = 0 → element already exists
// Even if equals = false!
// RECOMMENDED:
// (x.compareTo(y) == 0) == (x.equals(y))
// If not consistent → data loss in TreeSet!
compareTo vs compare
// Comparable:
this.compareTo(other) // "I" compare with "another"
// Comparator:
compare(o1, o2) // "judge" compares two
Timsort
// Collections.sort() = Timsort
// → Hybrid of Merge Sort + Insertion Sort
// → Stable (equals preserve order)
// → O(n log n)
// → Requires comparator transitivity!
// Transitivity violation:
// A > B, B > C, but A < C
// → IllegalArgumentException!
Timsort — Java’s sorting algorithm. Stable (equal elements preserve their original order). Transitivity — if A > B and B > C, then necessarily A > C. Violation leads to IllegalArgumentException.
Production Experience
Real scenario: overflow
// Comparator for long ID
return (int)(o1.id - o2.id); // Cast to int!
// → With large IDs → wrong sign
// → Incorrect sorting
// Solution: Long.compare(o1.id, o2.id)
Best Practices
- Comparable = natural ordering (one per class)
- Comparator = multiple strategies
- Comparator.comparing → readable, safe
- Integer/Long.compare → overflow protection
- nullsFirst/nullsLast → null handling
- compareTo = equals for TreeSet/TreeMap
- Transitivity is mandatory!
Summary for Senior
- Comparable = internal, natural ordering
- Comparator = external, multiple strategies
- comparing/thenComparing → declarative API
- Overflow → Integer/Long.compare
- compareTo vs equals → critical for TreeSet
- Timsort → stable, requires transitivity
🎯 Interview Cheat Sheet
Must know:
Comparable— object compares itself (compareTo), one ordering per class (natural ordering)Comparator— external “judge” (compare), multiple sorting strategiesComparator.comparing().thenComparing().reversed()— declarative Java 8+ API- Subtraction
o1.id - o2.idis dangerous — int overflow; useInteger.compare()/Long.compare() TreeSet/TreeMapusecompareTo, NOTequals— inconsistency = data loss- Timsort — stable O(n log n) sorting, requires comparator transitivity
Comparator.nullsFirst()/nullsLast()— safe null handling
Frequent follow-up questions:
- Why is
o1.id - o2.iddangerous? — With large values, int overflow: -2 billion - 2 billion = -4 billion (out of bounds), result has the wrong sign. - What if
compareToandequalsare not consistent in TreeSet? —compareTo = 0means “element already exists”, even ifequals = false— data is lost. - What is comparator transitivity? — If A > B and B > C, then necessarily A > C. Violation →
IllegalArgumentExceptionfrom Timsort. - When NOT to use Comparable? — Multiple sorting variants, third-party class (cannot modify), context-dependent ordering (locale).
Red flags (DO NOT say):
- “Comparator defines the natural ordering of a class” — no, Comparable does that
- “Subtraction is a safe way to compare numbers” — no, int overflow breaks sorting
- “TreeSet uses equals for comparison” — no, TreeSet uses
compareTo - “You can violate comparator transitivity” — no, Timsort will throw
IllegalArgumentException
Related topics:
- [[25. What is the difference between Iterator and ListIterator]]
- [[31. What operations does the Collection interface support]]