Skip to content

24. Comparable, Comparator & Sorting in Java

Table of Contents


Java provides two main strategies for sorting and comparing: Comparable (natural ordering) and Comparator (custom ordering).

Understanding their rules, constraints, and interactions with generics is essential.

  • For numeric types, sorting follows natural numerical order, meaning smaller values come before larger ones.
  • Sorting strings follows lexicographical (Unicode code point) order: character-by-character comparison; digits come before uppercase, uppercase before lowercase.

This ordering is based on each character’s Unicode code point, not alphabetical intuition.

A Unicode code point is a unique numerical value assigned to a character in the Unicode standard.

More precisely: a Unicode code point is an integer (written in hexadecimal as U+XXXX) that represents a specific character, symbol, or control mark—independent of font, language, or platform.

  • Examples:
    • U+0041 → A
    • U+0061 → a
    • U+0030 → 0
    • U+1F600 → 😀

A code point is not a byte sequence. It’s an abstract number.

How a code point is stored in memory depends on the encoding (UTF-8, UTF-16, UTF-32).

Unicode defines code points from U+0000 to U+10FFFF.

In short: Unicode code points define what the character is; encodings define how it is represented in bytes.

  • Example natural ordering
List<String> items = List.of("10", "2", "A", "Z", "a", "b");

List<String> sorted = new ArrayList<>(items);
Collections.sort(sorted);

System.out.println(sorted);

Output:

[10, 2, A, Z, a, b]

Note

Natural ordering is only defined for types that implement Comparable.

24.1 Comparable — Natural Ordering

The interface Comparable<T> defines the natural order of a type.

A class implements it when it wants to define its default sorting rule.

24.1.1 Comparable Method Contract

public interface Comparable<T> {
    int compareTo(T other);
}

Rules and expectations:

  • Return negativethis < other
  • Return zerothis == other
  • Return positivethis > other

Important

  • Natural ordering must be consistent with equals(), unless explicitly documented otherwise:
  • compareTo() is consistent with equals() if, and only if, a.compareTo(b) == 0 and a.equals(b) is true.

Warning

compareTo may throw ClassCastException if given a non-comparable type — but this usually appears only with raw types.

24.1.2 Example: Class Implementing Comparable

public class Person implements Comparable<Person> {

    private String name;
    private int age;

    public Person(String n, int a) {
        this.name = n;
        this.age = a;
    }

    @Override
    public int compareTo(Person other) {
        return Integer.compare(this.age, other.age);
    }


}

var list = List.of(new Person("Bob", 40), new Person("Alice", 30));

list.stream().sorted().forEach(p -> System.out.println(p.getAge()));

The list sorts by age, because that is the natural numbering order.

24.1.3 Common Comparable Pitfalls

  • Compare all relevant fields → inconsistent results if not
  • Violating transitivity → leads to undefined behavior
  • Throwing exceptions inside compareTo() breaks sorting
  • Failing to implement the same logic as equals() → common trap

24.2 Comparator — Custom Ordering

The interface Comparator<T> allows defining multiple sorting strategies without modifying the class itself.

24.2.1 Comparator Core Methods

int compare(T a, T b);

Additional helper methods:

24.2.1.1 Comparator Helper Static Methods

Method Static / Instance Return Type Parameters Description
Comparator.comparing(keyExtractor) static Comparator Function<? super T, ? extends U> Builds a comparator comparing extracted keys using natural ordering.
Comparator.comparing(keyExtractor, keyComparator) static Comparator Function, Comparator Builds comparator comparing extracted keys using a custom comparator.
Comparator.comparingInt(keyExtractor) static Comparator ToIntFunction Optimized comparator for int keys (avoids boxing).
Comparator.comparingLong(keyExtractor) static Comparator ToLongFunction Optimized comparator for long keys.
Comparator.comparingDouble(keyExtractor) static Comparator ToDoubleFunction Optimized comparator for double keys.
Comparator.naturalOrder() static Comparator none Comparator using natural ordering (Comparable).
Comparator.reverseOrder() static Comparator none Reverse natural ordering.
Comparator.nullsFirst(comparator) static Comparator Comparator Wraps comparator so nulls compare before non-nulls.
Comparator.nullsLast(comparator) static Comparator Comparator Wraps comparator so nulls compare after non-nulls.

24.2.1.2 Instance Methods on Comparator

Method Static / Instance Return Type Parameters Description
thenComparing(otherComparator) instance Comparator Comparator Adds a secondary comparator when the primary compares equal.
thenComparing(keyExtractor) instance Comparator Function Secondary comparison using natural ordering of extracted key.
thenComparing(keyExtractor, keyComparator) instance Comparator Function, Comparator Secondary comparison with custom comparator.
thenComparingInt(keyExtractor) instance Comparator ToIntFunction Secondary numeric comparison (optimized).
thenComparingLong(keyExtractor) instance Comparator ToLongFunction Secondary numeric comparison.
thenComparingDouble(keyExtractor) instance Comparator ToDoubleFunction Secondary numeric comparison.
reversed() instance Comparator none Returns a reversed comparator for the same comparison logic.

24.2.2 Comparator Example

var people = List.of(new Person("Bob",40), new Person("Ann",30));

Comparator<Person> byName = Comparator.comparing(Person::getName);

Comparator<Person> byAgeDesc = Comparator.comparingInt(Person::getAge).reversed();

var sorted = people.stream().sorted(byName.thenComparing(byAgeDesc)).toList();

24.3 Comparable vs Comparator

Feature Comparable Comparator
Package java.lang java.util
Method compareTo(T) compare(T,T)
Sorting Type Natural (default) Custom (multiple strategies)
Modifies Source Class YES NO
Useful For Default ordering External or alternate ordering
Allows Multiple Orders NO YES
Used By Collections.sort YES YES
Used By Arrays.sort YES YES

24.4 Sorting Arrays and Collections

24.4.1 Arrays sort()

int[] nums = {3,1,2};
Arrays.sort(nums); // natural order

Person[] arr = {...};
Arrays.sort(arr); // Person must implement Comparable
Arrays.sort(arr, byName); // using Comparator

24.4.2 Collections sort()

Collections.sort(list); // natural order
Collections.sort(list, byName); // comparator

Note

Collections.sort(list) delegates to list.sort(comparator) since Java 8.


24.5 Multi-Level Sorting (thenComparing)

var cmp = Comparator
    .comparing(Person::getLastName)
        .thenComparing(Person::getFirstName)
            .thenComparingInt(Person::getAge);

24.6 Comparing Primitives Efficiently

Comparator.comparingInt(Person::getAge)
Comparator.comparingLong(...)
Comparator.comparingDouble(...)

Note

These avoid boxing and are preferred in performance-sensitive code.


24.7 Common Traps

  • Sorting a list of Objects without Comparable → runtime ClassCastException
  • compareTo inconsistent with equals → unpredictable behavior
  • Comparator that breaks transitivity → sorting becomes undefined
  • Null elements → unless Comparator handles them, sorting throws NPE
  • Comparator comparing fields of mixed types → ClassCastException
  • Using subtraction to compare ints can overflow → always use Integer.compare()
  • Sorting a list with null elements and natural order → NPE
  • compareTo must never return inconsistent negative/zero/positive on same two objects (no randomness)

24.8 Full Example

record Book(String title, double price, int year) {}

var books = List.of(
new Book("Java 17", 40.0, 2021),
new Book("Algorithms", 55.0, 2019),
new Book("Java 21", 42.0, 2023)
);

Comparator<Book> cmp =
Comparator
    .comparingDouble(Book::price)
        .thenComparing(Book::year)
            .reversed();

books.stream().sorted(cmp)
    .forEach(System.out::println);

Note

reversed() applies to the entire composed comparator, not just the first comparison key.


24.9 Summary

  • Use Comparable for natural ordering (1 default order).
  • Use Comparator for flexible or multiple sorting strategies.
  • Comparators can compose (reversed, thenComparing).
  • Sorting requires consistent comparison logic.
  • Arrays.sort and Collections.sort use both Comparable and Comparator.