How does a type's natural ordering relate to equality, and how do you choose between Comparable and Comparator?
answer
- Natural order = the single default (Comparable)
- Comparator = external, many orders, type you can't change
- Same sign convention for both
- No Comparable + sort() => ClassCastException
- Comparator.comparing().thenComparing().reversed()
basics
~20 sNatural ordering is the single default order a type defines via Comparable. Ideally it matches equals. Use Comparable for the one canonical order; use a Comparator passed in when you need other orderings or can't change the type.
solid answer
~50 sNatural ordering is the one default ordering a type defines by implementing Comparable; sorted collections and Collections.sort use it when no Comparator is given. Ideally it is consistent with equals so TreeSet and HashSet agree on duplicates. Choose Comparable when the type has a single, obvious, canonical order it owns (numbers numerically, strings lexicographically, dates chronologically). Choose a Comparator when you need a different or context-specific ordering (sort people by age here, by name there), when a type legitimately has no single natural order, or when you cannot modify the type (third-party or final classes). Comparator is more flexible: you compose it with Comparator.comparing(...).thenComparing(...), reverse it with reversed(), and handle nulls with nullsFirst/nullsLast. A common rule: give a type a natural ordering only if there is one order everyone would expect; otherwise force callers to be explicit with a Comparator.
go deeper
Knows Comparable gives a default order and Comparator lets you sort differently; can call sort with both forms.
Chooses appropriately between Comparable and Comparator, composes comparators with thenComparing/reversed, and links natural ordering to consistency with equals.
Treats adding Comparable as an API commitment, reasons about TreeSet/HashSet interchangeability, and handles nulls/reversal cleanly with Comparator combinators.
Sets conventions on when a domain type earns a natural ordering vs. mandating explicit comparators, and considers the long-term cost of changing a published natural order.
## Natural ordering, defined A type's **natural ordering** is the *single, default* way its instances are ordered — the order you get when you don't specify anything else. It is established by the type **implementing `Comparable`**. Examples baked into the JDK: - `Integer`, `Double` — numeric (ascending). - `String` — lexicographic, comparing UTF-16 code units one by one. - `LocalDate`, `Instant` — chronological (earlier before later). - enums — **declaration order** (the order constants are written, exposed via `ordinal()`). When you call `Collections.sort(list)`, `list.sort(null)`, or build a `TreeSet<>()` with no comparator, the elements are arranged by their natural ordering. If the element type is **not** `Comparable`, those calls throw `ClassCastException` at runtime. ## How natural ordering relates to equality Natural ordering and `equals` are *separate* mechanisms, but they should usually agree: `compareTo == 0` exactly when `equals == true` (consistency with equals). When they agree, `TreeSet` and `HashSet` treat the same pairs as duplicates, so the two `Set` implementations are interchangeable. When they disagree (the `BigDecimal` 1.0-vs-1.00 case), sorted collections deduplicate by ordering while hash collections deduplicate by equality — a real, visible behavioral difference. So: design the natural ordering to mirror equality unless you have a documented reason not to. ## Comparable vs Comparator — the two sides | | `Comparable<T>` | `Comparator<T>` | |---|---|---| | Where it lives | **on the type** (`class T implements Comparable<T>`) | a **separate object** you pass in | | Method | `int compareTo(T o)` | `int compare(T a, T b)` | | How many orders | exactly **one** (the natural order) | as many as you like | | Modifies the type? | yes — you must edit/own the class | no — works on types you can't change | | Used by | `sort(list)`, `TreeSet<>()` with no comparator | `sort(list, cmp)`, `new TreeSet<>(cmp)` | Both use the **same sign convention** (negative/zero/positive). A `Comparator` is just the *externalized* form of the same idea. ## When to choose which **Reach for `Comparable` when:** - There is **one** order virtually everyone would expect for the type (money by amount, version by number, timestamp chronologically). - You own the type and want sorting/`TreeSet` to "just work" with no ceremony. **Reach for `Comparator` when:** - You need **multiple** or **context-dependent** orderings (by name here, by salary there). - The type has **no single obvious** order (a `Person` — by age? name? id?), so forcing callers to be explicit is clearer than picking one arbitrarily. - You **cannot modify** the type (third-party, `final`, or a class whose natural order is wrong for your use). - You want to **reverse**, compose, or null-handle an existing order — `Comparator` offers `reversed()`, `comparing(...).thenComparing(...)`, `nullsFirst(...)`, `nullsLast(...)`. ## Composing comparators (modern style) ```java List<Person> people = ...; people.sort( Comparator.comparingInt(Person::age) .thenComparing(Person::lastName) .reversed()); ``` These factory/combinator methods build correct, transitive, overflow-safe orderings without hand-written branching — the recommended way to express any non-trivial ordering, natural or not. ## Design guidance Giving a type a natural ordering is an **API commitment**: every sort and `TreeSet` will use it, and changing it later is a behavioral break. So add `Comparable` only when the order is genuinely canonical; when in doubt, leave it off and let callers supply a `Comparator`. That keeps ordering decisions explicit and local to where they're needed.
- You need to sort a third-party class you cannot modify. What do you use?A Comparator passed to sort(list, comparator) or new TreeSet<>(comparator). You cannot add Comparable to a class you don't own, but a Comparator is external and works on any type.
- What happens if you call Collections.sort(list) on a list whose elements don't implement Comparable?It throws ClassCastException at runtime, because sort with no Comparator casts elements to Comparable to use their natural ordering, which they don't have.
saying these in an interview costs you the question
- Saying every type should implement Comparable
- Thinking Comparator requires modifying the type (it doesn't)
- Believing natural ordering and equals must be identical objects rather than consistent results
- Forgetting that sort() on a non-Comparable type throws ClassCastException