Collections

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.

Java 21 (core language)

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);
    }
}

Sources