Back to Stream: Sorting & Comparator
Q5Question 5 of 23

Comparable.compareTo vs Comparator.compare: natural ordering vs external strategy

Key Concepts
  • Comparable defines ONE natural ordering INSIDE the class via compareTo: the default for sorted(), TreeSet, and TreeMap
  • Comparator defines UNLIMITED external ordering strategies OUTSIDE the class via compare: no class modification needed
  • Comparator.naturalOrder() wraps compareTo as a Comparator, bridging the two worlds
  • Comparator.reverseOrder() wraps compareTo in reverse for descending natural order
  • Use Comparable when the class has one obvious default sort order (employee by ID)
  • Use Comparator for all other sort orders (by salary, by name, by department)
  • Comparator follows the Strategy Pattern: different sort strategies without modifying the sorted class

Definition

Comparable defines one natural ordering inside the class via compareTo, while Comparator defines unlimited external ordering strategies outside the class via compare.
  • Comparable – implemented BY the class; defines the natural ordering via compareTo(T other). One per class.
  • Comparator – implemented OUTSIDE the class; defines an external sorting strategy via compare(T a, T b). Unlimited per class.
  • naturalOrder() – returns a Comparator that delegates to the element's compareTo method, bridging Comparable to Comparator.
  • reverseOrder() – returns a Comparator that is the reverse of the natural ordering.
  • Strategy Pattern – Comparator is an interchangeable sorting strategy; different screens can use different Comparators for the same data.

Comparable is the bank's default way to sort employees: by employee ID (built into the Employee class). There is only one default. Comparator is an external sorting instruction the manager writes on a separate paper: sort by salary descending, sort by department then name. The manager can write unlimited instructions without changing the Employee class.

Understanding the Concepts

What is the difference between Comparable and Comparator?

What is Comparable?

Comparable is implemented by the class that wants a default sort order. The class implements Comparable and overrides compareTo(T other).

JAVA
package com.bank.model;

import java.math.BigDecimal;

public class Trade implements Comparable<Trade> {
    private String tradeId;
    private BigDecimal amount;

    @Override
    public int compareTo(Trade other) {
        return this.tradeId.compareTo(other.tradeId);  // natural order = by tradeId
    }
}
JAVA
// Natural order used by:
Collections.sort(trades);                    // uses compareTo
trades.stream().sorted().toList();           // uses compareTo
new TreeSet<>(trades);                       // natural order

What is Comparator?

Comparator is defined outside the class. No modification to Trade is needed.

JAVA
// External Comparators:
Comparator<Trade> byAmount = Comparator.comparing(Trade::getAmount);
Comparator<Trade> byAmountDesc = Comparator.comparing(Trade::getAmount).reversed();
Comparator<Trade> bySymbolThenAmount = Comparator.comparing(Trade::getSymbol)
    .thenComparing(Trade::getAmount);

// Usage:
trades.sort(byAmount);
trades.stream().sorted(byAmountDesc).toList();
new TreeSet<>(bySymbolThenAmount);

How do naturalOrder and reverseOrder bridge the two?

JAVA
// naturalOrder: wraps Comparable.compareTo as a Comparator
Comparator<String> natural = Comparator.naturalOrder();
// Equivalent to: (a, b) -> a.compareTo(b)

// Used in nullsLast (which requires a Comparator argument)
Comparator<Trade> deptSort = Comparator.comparing(Trade::getDepartment,
    Comparator.nullsLast(Comparator.naturalOrder()));

// reverseOrder: reverse of natural ordering
Comparator<String> reversed = Comparator.reverseOrder();
trades.stream()
    .sorted(Comparator.comparing(Trade::getAmount, Comparator.reverseOrder()))
    .toList();

When do you use Comparable vs Comparator?

| Aspect | Comparable | Comparator |
|---|---|---|
| Defined | Inside the class | Outside the class |
| How many | One per class | Unlimited per class |
| Interface | compareTo(T other) | compare(T a, T b) |
| Modification | Requires changing the class | No class change needed |
| Default for | sorted(), TreeSet, TreeMap | Passed as parameter |
| Example | String alphabetical order | Sort strings by length |

What are common mistakes?

JAVA
// WRONG: trying to sort a class that does not implement Comparable
class Account { String id; BigDecimal balance; }
List<Account> accounts = List.of(new Account(), new Account());
Collections.sort(accounts);  // ClassCastException: Account cannot be cast to Comparable

// CORRECT: provide a Comparator
accounts.sort(Comparator.comparing(Account::getBalance));

What should a senior engineer know?

  • Comparable should be consistent with equals: if a.compareTo(b) == 0, then a.equals(b) should be true. TreeSet uses compareTo for uniqueness, not equals.
  • Comparator.comparing is the modern way to create comparators; avoid writing compare() by hand.
  • The Comparator fluent API (comparing, thenComparing, reversed) follows the Strategy Pattern.

What are common interview follow-up questions?

  • Can a class implement Comparable and still be sorted with a Comparator? (Yes. The Comparator overrides the natural order.)
  • What happens if compareTo is inconsistent with equals? (TreeSet may behave differently from HashSet for the same elements.)
  • Is Comparable required for TreeSet? (Either the elements must be Comparable, or you must provide a Comparator to the TreeSet constructor.)

Visual Overview

DIAGRAM
  Comparable vs Comparator:

  Comparable (IN the class):
  ┌──────────────────────────┐
  │ class Trade implements   │
  │   Comparable<Trade> {    │
  │   compareTo(other) {     │
  │     return id.compareTo  │
  │       (other.id);        │
  │   }                      │
  │ }                        │
  │ ONE natural order        │
  └──────────────────────────┘

  Comparator (OUTSIDE the class):
  ┌──────────────────────────┐
  │ byAmount = comparing     │
  │   (Trade::getAmount)     │
  │ bySymbol = comparing     │
  │   (Trade::getSymbol)     │
  │ byDept = comparing       │
  │   (Trade::getDepartment) │
  │ UNLIMITED strategies     │
  └──────────────────────────┘

Key Takeaway

Comparable defines one natural ordering inside the class via compareTo(). It is the default for sorted(), TreeSet, TreeMap, and Collections.sort(). String's natural order is alphabetical, Integer's is numeric. You implement Comparable when the class has one obvious default sort order.

Comparator defines unlimited external ordering strategies outside the class via compare(). You create Comparators with Comparator.comparing(), lambdas, or the fluent chain API. No class modification is needed. Different screens can use different Comparators for the same data.

Comparator.naturalOrder() wraps the element's compareTo as a Comparator, bridging the two worlds. It is essential when APIs require a Comparator but you want natural ordering, such as nullsLast(naturalOrder()). Comparator.reverseOrder() wraps compareTo in reverse.

Remember one sentence: Comparable defines one natural order inside the class; Comparator defines unlimited external orders outside it, and naturalOrder() bridges them.

---

Share
Use ← → arrow keys to navigate