6.5 Sorting, Comparable, and Comparator

Key Takeaways

  • Comparable<T> defines the natural ordering of a class via int compareTo(T o), returning negative, zero, or positive integer values.
  • Comparator<T> defines customizable external sorting strategies via compare(T o1, T o2) and supports rich composition with comparing, thenComparing, and reversed.
  • Collections.binarySearch and Arrays.binarySearch require collections/arrays to be pre-sorted according to the searching comparator; otherwise, search results are undefined.
  • Binary search returns the non-negative index if found, or (-(insertion point) - 1) if absent, allowing exact deduction of where the searched element belongs.
  • Direct integer subtraction (o1.val - o2.val) in comparators is an anti-pattern prone to integer overflow; always use Integer.compare(a, b).
Last updated: September 2026

Sorting, Comparable, and Comparator

Sorting, ordering, and binary searching are core capabilities of the Java platform. The Oracle Certified Professional: Java SE 21 Developer (1Z0-830) exam tests your understanding of natural ordering with Comparable, flexible multi-criteria sorting using Comparator factory and chaining methods, in-place sorting APIs, and the exact return value contract of Collections.binarySearch and Arrays.binarySearch.


1. Natural Ordering with Comparable<T>

The java.lang.Comparable<T> interface imposes a natural ordering on the objects of each class that implements it.

package java.lang;

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

The compareTo Contract

  • Returns a negative integer if this < o.
  • Returns zero (0) if this is equal to o.
  • Returns a positive integer if this > o.
  • Symmetry: sgn(x.compareTo(y)) == -sgn(y.compareTo(x)).
  • Transitivity: If x.compareTo(y) > 0 and y.compareTo(z) > 0, then x.compareTo(z) > 0.
  • Null Handling: Must throw NullPointerException if o is null.
public record Employee(int id, String name, double salary) implements Comparable<Employee> {
    @Override
    public int compareTo(Employee other) {
        // Safe integer comparison avoiding arithmetic overflow
        return Integer.compare(this.id, other.id);
    }
}

Consistency with equals

A natural ordering is said to be consistent with equals if and only if: (x.compareTo(y)==0)    (x.equals(y))(x.\text{compareTo}(y) == 0) \iff (x.\text{equals}(y))

While not strictly required by the compiler, violating this rule creates unexpected behavior in sorted collections (TreeSet, TreeMap) which rely exclusively on compareTo rather than equals to determine element uniqueness:

// BigDecimal: compareTo checks numeric value, equals checks numeric value AND scale!
BigDecimal d1 = new BigDecimal("2.0");
BigDecimal d2 = new BigDecimal("2.00");

Set<BigDecimal> hashSet = new HashSet<>();
hashSet.add(d1); hashSet.add(d2); // size = 2 (d1.equals(d2) is false)

Set<BigDecimal> treeSet = new TreeSet<>();
treeSet.add(d1); treeSet.add(d2); // size = 1 (d1.compareTo(d2) == 0 -> considered duplicate!)

2. Custom Ordering with Comparator<T>

The java.util.Comparator<T> functional interface enables custom sorting logic external to the class definition:

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}

Static Factory Methods on Comparator

Java provides static helper methods to construct comparators declaratively:

Factory MethodDescription
Comparator.naturalOrder()Sorts according to natural Comparable order
Comparator.reverseOrder()Sorts in reverse of natural order
Comparator.comparing(keyExtractor)Extracts a Comparable sort key (e.g., Employee::name)
Comparator.comparing(keyExtractor, keyComp)Extracts key and compares using custom comparator
Comparator.comparingInt(toIntFunc)Extracts primitive int to avoid autoboxing overhead
Comparator.comparingLong(toLongFunc)Extracts primitive long
Comparator.comparingDouble(toDoubleFunc)Extracts primitive double
Comparator.nullsFirst(comp)Places null elements at the beginning
Comparator.nullsLast(comp)Places null elements at the end

3. Comparator Chaining and Combinator Methods

Complex multi-tier sorting criteria can be composed cleanly using default methods on Comparator:

// Multi-level sort: Sort by Department ASC, then by Salary DESC, then by Name ASC
Comparator<Employee> comp = Comparator
    .comparing(Employee::department)
    .thenComparing(Comparator.comparingDouble(Employee::salary).reversed())
    .thenComparing(Employee::name);

List<Employee> staff = getEmployees();
staff.sort(comp); // In-place sort using List.sort

Precedence and the .reversed() Placement Trap

The .reversed() method inverts the comparator instance on which it is called. Its placement in a chain dramatically affects the sorting order:

// Trap 1: reversed() at the end inverts the ENTIRE composite comparator!
Comparator<Employee> c1 = Comparator.comparing(Employee::department)
                                    .thenComparing(Employee::salary)
                                    .reversed(); 
// Result: Department DESC, then Salary DESC

// Trap 2: reversed() attached to the first comparator inverts ONLY that tier
Comparator<Employee> c2 = Comparator.comparing(Employee::department)
                                    .reversed()
                                    .thenComparing(Employee::salary);
// Result: Department DESC, then Salary ASC

Null-Safe Sorting

When collections contain null elements, using a plain comparator throws NullPointerException. Wrap the comparator with nullsFirst or nullsLast:

// Sort names alphabetically, placing null entries at the very end
Comparator<String> safeComp = Comparator.nullsLast(Comparator.naturalOrder());

List<String> names = new ArrayList<>(Arrays.asList("Charlie", null, "Alice", "Bob"));
names.sort(safeComp);
System.out.println(names); // [Alice, Bob, Charlie, null]

4. Sorting APIs in Modern Java

API MethodTargetIn-Place?Null Comparator MeaningSorting Algorithm
list.sort(comparator)List<E>YesUses natural ordering (Comparable)Timsort (Stable, $O(n \log n)$)
Collections.sort(list)List<T extends Comparable>YesUses natural orderingTimsort
Collections.sort(list, comp)List<T>YesUses custom ComparatorTimsort
Arrays.sort(array)T[] (Objects)YesUses natural orderingTimsort (Stable)
Arrays.sort(primitiveArray)Primitives (int[], etc.)YesN/ADual-Pivot Quicksort (Unstable)
Arrays.parallelSort(array)T[] or PrimitivesYesNatural or CustomParallel Merge-Sort

5. Searching Sorted Collections: binarySearch

Collections.binarySearch and Arrays.binarySearch perform fast $O(\log n)$ searches on sorted datasets.

Mandatory Preconditions

  1. The collection/array MUST be sorted in ascending order prior to calling binarySearch.
  2. If sorted using a custom Comparator, the exact same comparator must be passed to binarySearch(list, key, comparator).
  3. If the list is unsorted or sorted in reverse without passing the reverse comparator, the return value is undefined.
  4. If searching a LinkedList, binarySearch degrades to $O(n)$ time due to sequential node traversal.

Return Value Mathematical Formula

  • Key Found: Returns the index ($i \ge 0$) of the search key. If multiple matching elements exist, which matching index is returned is not guaranteed.
  • Key Not Found: Returns a negative integer: Return Value=(insertion point)1\text{Return Value} = -(\text{insertion point}) - 1
  • Insertion Point Definition: The index of the first element greater than the search key, or list.size() if all elements in the list are smaller than the search key.
  • Deducing Insertion Point from Return Value: Insertion Point=(Return Value+1)\text{Insertion Point} = -(\text{Return Value} + 1)
List<Integer> list = List.of(10, 20, 30, 50, 60);

// Search for 30 (Present at index 2)
int idx1 = Collections.binarySearch(list, 30); // returns 2

// Search for 40 (Absent; would be inserted at index 3)
// Formula: -(3) - 1 = -4
int idx2 = Collections.binarySearch(list, 40); // returns -4

// Search for 5 (Absent; would be inserted at index 0)
// Formula: -(0) - 1 = -1
int idx3 = Collections.binarySearch(list, 5);  // returns -1

// Search for 100 (Absent; would be inserted at index 5)
// Formula: -(5) - 1 = -6
int idx4 = Collections.binarySearch(list, 100);// returns -6

6. Common Sorting Pitfalls and Anti-Patterns

1. Integer Subtraction Overflow Anti-Pattern

Writing a comparator using raw arithmetic subtraction:

// DANGEROUS ANTI-PATTERN!
Comparator<Integer> badComp = (a, b) -> a - b;

If a = Integer.MIN_VALUE ($-2,147,483,648$) and b = 1, a - b overflows to $+2,147,483,647$ (positive!), falsely indicating that $a > b$. Always use Integer.compare(a, b).

2. Modifying Sort Keys of Elements in TreeSet / TreeMap

If an object is added to a TreeSet and its sort key field is subsequently modified, the tree's internal ordering invariants are corrupted. The collection can no longer find or remove the modified element, leading to severe memory leaks and data corruption.

Loading diagram...
Comparator Composition Pipeline and Binary Search Return Value Calculation
Test Your Knowledge

Given the sorted list of integers [10, 25, 40, 55, 70], what is the return value of Collections.binarySearch(list, 30)?

A
B
C
D
Test Your Knowledge

Consider the following comparator definition and list sort operation: Comparator<String> comp = Comparator.comparingInt(String::length) .reversed() .thenComparing(Comparator.naturalOrder()); List<String> words = new ArrayList<>(List.of("pear", "apple", "fig", "banana", "kiwi", "plum")); words.sort(comp); System.out.println(words); What is the output of this program?

A
B
C
D
Test Your Knowledge

A developer stores instances of a Person class in a TreeSet. The Person class implements Comparable but defines compareTo based only on 'id', while equals and hashCode are based on both 'id' and 'name'. What happens when adding two Person objects with the same 'id' but different 'name' values to the TreeSet?

A
B
C
D
Test Your Knowledge

Why is the subtraction expression (o1, o2) -> o1.getValue() - o2.getValue() considered an anti-pattern when writing a Comparator for integers?

A
B
C
D