Leepoint Java Reference Notes on the Java language and its standard library.

Comparators

Comparable is how a class sorts itself. Comparator is every other order you might want.

Comparable is implemented by the class and defines its one natural order:

public class Person implements Comparable<Person> {
    @Override
    public int compareTo(Person other) {
        return this.lastName.compareTo(other.lastName);
    }
}

A Comparator is a separate object defining some other order, and you can have as many as you need. Use it when the class is not yours to change, when there is no obviously natural order, or when you need several orderings.

Building them

Rarely written by hand any more:

people.sort(Comparator.comparing(Person::lastName));

people.sort(Comparator.comparing(Person::lastName)
                      .thenComparing(Person::firstName));

people.sort(Comparator.comparingInt(Person::age).reversed());

people.sort(Comparator.comparing(Person::department)
                      .thenComparing(Person::age, Comparator.reverseOrder()));

thenComparing is the tie-breaker, applied only when the previous comparison returns zero.

Note where reversed() sits. It reverses everything before it. Reversing a two-key comparator flips both keys, which is usually not what you meant. To reverse only the second key, pass a reversed comparator to thenComparing, as in the last example.

Use comparingInt for numbers

Comparator.comparingInt(Person::age)      // yes
(a, b) -> a.age() - b.age()               // no

The subtraction version overflows. If one age is near Integer.MAX_VALUE and the other is negative, the result wraps and the sign is wrong: the comparator then claims a larger value is smaller. It works on every test with sensible data and fails on the one row that is not.

comparingInt, comparingLong and comparingDouble also avoid boxing.

Nulls

Comparator.nullsFirst(Comparator.comparing(Person::lastName))

Without this, a null in the collection throws mid-sort, potentially leaving it partly reordered.

The contract

A comparator must be consistent: transitive, and with compare(a, b) the opposite sign of compare(b, a). Break it and sorting throws IllegalArgumentException: Comparison method violates its general contract. An error most people meet for the first time with no idea what it means.

The usual causes are subtraction overflow, comparing floating-point values that may be NaN, and a comparator whose result depends on mutable state that changes during the sort.

In sorted collections

TreeMap<Person, String> map = new TreeMap<>(byLastName);

Here the comparator defines equality as far as the collection is concerned. Two people the comparator calls equal are one key, even if equals disagrees, which is why a comparator used with TreeSet or TreeMap should normally be consistent with equals.