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 as0
are treated as duplicates even ifequals()
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.