67. Comparable vs Comparator
Comparable<T> defines a class's natural ordering (in-class); Comparator<T> defines an external, reusable ordering. Comparator.comparing(keyExtractor) and chained thenComparing enable fluent multi-key sorting.
Explanation
Comparable<T>: implement compareTo(T other) inside the class. Returns negative (this < other), zero (equal), positive (this > other). This is the class's natural ordering โ used by Collections.sort(list), Arrays.sort(arr), and TreeSet/TreeMap by default.
Comparator<T>: a separate functional interface compare(T a, T b) with the same contract. Passed explicitly to sort methods or collections. Multiple Comparators can exist for the same class, enabling different sort orders without modifying the class.
Modern style: Comparator.comparing(Person::lastName).thenComparing(Person::firstName).thenComparingInt(Person::age). The reversed() method flips order. Comparator.naturalOrder() and reverseOrder() are convenient constants. nullsFirst(comparator) / nullsLast(comparator) handle null values gracefully.
Example
import java.util.*;
public class ComparableVsComparator {
// Comparable: natural order is by name
record Person(String firstName, String lastName, int age)
implements Comparable<Person> {
@Override
public int compareTo(Person other) {
int lastCmp = lastName.compareTo(other.lastName);
if (lastCmp != 0) return lastCmp;
return firstName.compareTo(other.firstName);
}
}
public static void main(String[] args) {
var people = new ArrayList<>(List.of(
new Person("Bob", "Smith", 35),
new Person("Alice", "Jones", 28),
new Person("Carol", "Smith", 42),
new Person("Dave", "Jones", 31)
));
// Natural order (Comparable.compareTo)
Collections.sort(people);
people.forEach(p -> System.out.println(p.lastName() + ", " + p.firstName()));
System.out.println();
// Comparator: external, different orderings
Comparator<Person> byAge = Comparator.comparingInt(Person::age);
Comparator<Person> byAgeDesc = byAge.reversed();
Comparator<Person> byLastFirst = Comparator.comparing(Person::lastName)
.thenComparing(Person::firstName)
.thenComparingInt(Person::age);
people.sort(byAgeDesc);
System.out.println("By age descending:");
people.forEach(p -> System.out.println(" " + p.firstName() + " " + p.age()));
// TreeSet with custom Comparator
TreeSet<Person> byAgeSet = new TreeSet<>(byAge);
byAgeSet.addAll(people);
System.out.println("\nTreeSet by age: " + byAgeSet.first().firstName()
+ " -> " + byAgeSet.last().firstName());
// nullsFirst
List<String> withNulls = new ArrayList<>(Arrays.asList("b", null, "a", null, "c"));
withNulls.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
System.out.println("Nulls first: " + withNulls);
}
}