Question 29 · Section 4

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() —...

Language versions: English Russian Ukrainian

🟢 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

  1. Need multiple sorting variants of the same class — use Comparator
  2. Class from a third-party library (cannot modify) — use Comparator
  3. 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

  1. Comparable = natural ordering (one per class)
  2. Comparator = multiple strategies
  3. Comparator.comparing → readable, safe
  4. Integer/Long.compare → overflow protection
  5. nullsFirst/nullsLast → null handling
  6. compareTo = equals for TreeSet/TreeMap
  7. 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 strategies
  • Comparator.comparing().thenComparing().reversed() — declarative Java 8+ API
  • Subtraction o1.id - o2.id is dangerous — int overflow; use Integer.compare()/Long.compare()
  • TreeSet/TreeMap use compareTo, NOT equals — 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.id dangerous? — With large values, int overflow: -2 billion - 2 billion = -4 billion (out of bounds), result has the wrong sign.
  • What if compareTo and equals are not consistent in TreeSet?compareTo = 0 means “element already exists”, even if equals = false — data is lost.
  • What is comparator transitivity? — If A > B and B > C, then necessarily A > C. Violation → IllegalArgumentException from 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]]