🗃️ Collections Framework · Intermediate

Comparable vs Comparator in Java

Natural ordering vs external ordering; Comparator.comparing().thenComparing().reversed().

🧩 The mysterySort people by age, then by name, then flip the whole thing, all in one readable line. Plus: the one-character compareTo bug that has crashed real sorts.

Built-in order vs plug-in order

**Comparable gives a class its natural ordering** from the inside, via compareTo(other). **Comparator defines an ordering from outside**, via compare(a, b), so one type can have many orders. Both return negative, zero or positive.

// inside the class: one natural order
record Ver(int n) implements Comparable<Ver> {
    public int compareTo(Ver o) {
        return Integer.compare(n, o.n);
    }
}

Who uses natural order?

Collections.sort, List.sort(null), **TreeSet and TreeMap** fall back to compareTo when you don't supply a Comparator. String, Integer and LocalDate all implement Comparable. For any other order, build a Comparator like Lego: **comparing(key), then thenComparing(key2)** to break ties.

people.sort(Comparator
    .comparing(Person::city)
    .thenComparing(Person::name));
🔮 Predict it

Your turn

Sort by age, ties broken by name. What prints?

record P(String name, int age) {}
void main() {
    var ps = new ArrayList<>(List.of(
        new P("Zed", 20), new P("Amy", 30),
        new P("Bea", 20)));
    ps.sort(Comparator.comparingInt(P::age)
        .thenComparing(P::name));
    for (P p : ps) IO.print(p.name() + " ");
}
  1. Zed Amy Bea
  2. Bea Zed Amy
  3. Amy Bea Zed
Show the answer

Age first: the two 20-year-olds come before Amy (30). Between them, thenComparing(P::name) puts Bea before Zed.

reversed() flips everything before it

**reversed() wraps the whole chain built so far**, not just the last key. To reverse only one key, give that key its own order: thenComparing(key, Comparator.reverseOrder()).

comparing(P::age).thenComparing(P::name)
    .reversed();   // age AND name reversed
comparing(P::age).thenComparing(P::name,
    Comparator.reverseOrder()); // name only
🔮 Predict it

Flip it

Same people, now with .reversed() at the end. What prints?

record P(String name, int age) {}
void main() {
    var ps = new ArrayList<>(List.of(
        new P("Zed", 20), new P("Amy", 30),
        new P("Bea", 20)));
    ps.sort(Comparator.comparingInt(P::age)
        .thenComparing(P::name).reversed());
    for (P p : ps) IO.print(p.name() + " ");
}
  1. Amy Bea Zed
  2. Amy Zed Bea
  3. Bea Zed Amy
Show the answer

Both keys flip: oldest first (Amy), then the 20-year-olds by name in reverse: Zed before Bea.

Comparing ints

✗ Overflows
public int compareTo(Temp o) {
    return v - o.v;
}

With v = Integer.MIN_VALUE and o.v = 1, the subtraction wraps to a huge positive number: the wrong sign.

✓ Always safe
public int compareTo(Temp o) {
    return Integer.compare(v, o.v);
}

Integer.compare returns -1, 0 or 1 and never overflows.

💼 In the real world

In real code

Every sortable table in an app, every "sort by price, then rating" button, every TreeMap key is a Comparator decision. A broken comparator can even crash a sort: Java's sort may throw IllegalArgumentException: Comparison method violates its general contract! when the results are inconsistent.

Key takeaways

  1. compareTo / compare return negative, zero or positive
  2. Comparable: one built-in order (String, Integer, LocalDate…)
  3. Comparator: any number of external orders
  4. reversed() reverses everything before it in the chain

💡 Comparable is a person's height written on their ID; a Comparator is a judge who can rank people by height, age or name.

🤯 Did you know?

Java sorts objects with TimSort, an algorithm Tim Peters invented for Python in 2002. Java adopted it for object sorting in Java 7.

Practice questions

What does this print?

record P(String name, int age) {}
void main() {
    var ps = new ArrayList<>(List.of(new P("Bo", 30),
        new P("Cy", 25), new P("Al", 25)));
    ps.sort(Comparator.comparingInt(P::age)
        .thenComparing(P::name));
    ps.forEach(p -> System.out.print(p.name() + " "));
}
  1. Bo Cy Al
  2. Al Cy Bo
  3. Al Bo Cy
  4. Cy Al Bo
Check your answer

Al Cy Bo. First by age: the two 25-year-olds come before Bo (30). The tie between Cy and Al is broken by name, so Al comes first.

What does this print?

record P(String name, int age) {}
void main() {
    var ps = new ArrayList<>(List.of(new P("Bo", 30),
        new P("Cy", 25), new P("Al", 25)));
    ps.sort(Comparator.comparingInt(P::age)
        .thenComparing(P::name).reversed());
    ps.forEach(p -> System.out.print(p.name() + " "));
}
  1. Bo Al Cy
  2. Bo Cy Al
  3. Al Cy Bo
  4. Cy Al Bo
Check your answer

Bo Cy Al. reversed() applies to the entire comparator built so far — both age AND name are reversed. Oldest first (Bo), then names in reverse: Cy before Al.

Next: the Collections class (with an s) has a whole toolbox of one-liners, plus a read-only wrapper that isn't as read-only as it looks.