Comparator vs Comparable

TL;DR

  • Comparable → put the natural order inside the class (one order).
  • Comparator → define external strategies outside the class (many orders).

Core differences

  • Package & method
    • Comparable<T> (java.lang) → int compareTo(T o)
    • Comparator<T> (java.util) → int compare(T a, T b)
  • Ownership
    • Comparable: class implements it → order is baked in.
    • Comparator: separate object/lambda → order is pluggable.
  • Use in collections
    • Collections.sort(list) / Stream.sorted() uses Comparable when no comparator is given.
    • Collections.sort(list, cmp) / Stream.sorted(cmp) uses Comparator.
    • TreeSet/TreeMap: natural order (Comparable) OR custom order (Comparator in constructor).

When to use which

  • Comparable: there’s a single, obvious, stable “natural” order (e.g., numbers, dates, IDs, alphabetical).
  • Comparator: you need multiple sort views (by name, then age; by salary desc; locale-aware; nulls-first, etc.), or you can’t modify the class (3rd-party type).

Tiny examples

1) Comparable — natural order

public final class Person implements Comparable<Person> {
  private final long id;
  private final String name;

  // ctor/getters...

  @Override
  public int compareTo(Person other) {
    return Long.compare(this.id, other.id); // Natural order by id
  }
}

Usage:

List<Person> people = ...
Collections.sort(people);              // uses compareTo
// or
people.stream().sorted().toList();

2) Comparator — alternate orders (Java 8+)

Comparator<Person> byName =
    Comparator.comparing(Person::getName, Comparator.nullsLast(String::compareTo));

Comparator<Person> byNameThenId =
    Comparator.comparing(Person::getName)
              .thenComparingLong(Person::getId);

Comparator<Person> byIdDesc = Comparator.comparingLong(Person::getId).reversed();

Usage:

people.sort(byNameThenId);
// or
var sorted = people.stream().sorted(byIdDesc).toList();

3) TreeSet / TreeMap

var byNameSet = new java.util.TreeSet<>(byName);
byNameSet.addAll(people); // ordered by name

var map = new java.util.TreeMap<String, Person>(Comparator.nullsFirst(String::compareTo));

Contracts & pitfalls (important)

  • Return negative / zero / positive correctly; be transitive.
  • For SortedSet/SortedMap, prefer order consistent with equals; otherwise items that compare as 0 are treated as duplicates even if equals() says different.
  • Don’t implement Comparable if the “natural” order is controversial or changes over time (e.g., by mutable fields).
  • Use helpers:
    • Comparator.comparing(...), comparingInt/Long/Double, thenComparing(...), .reversed(), nullsFirst/nullsLast.
  • Avoid “subtraction compare” (overflow risk): prefer Integer.compare(a,b) / Long.compare(a,b).

Cheat sheet

  • One obvious order? Comparable.
  • Many/switchable orders? Comparator.
  • Need composition (then-by)? Comparator.thenComparing(...).
  • Need null handling? nullsFirst/nullsLast.
  • Sorted collections without mutating the class? Pass a Comparator to constructor.
Back to blog

Leave a comment

Please note, comments need to be approved before they are published.