Dataslope logoDataslope

Comparable and Comparator

Natural order vs. external order — how Java models "what does it mean for two values to be in order?"

A collection cannot sort what it cannot compare. So before any sort, TreeSet, TreeMap, or PriorityQueue works, something has to tell Java how two elements relate.

There are two ways to provide that ordering:

  • Comparable<T> — the type itself defines a natural order.
  • Comparator<T> — an external object defines an order on the type, often one of many possible orderings.

Comparable: the natural order

Comparable<T> has a single method, int compareTo(T other), which returns a negative number, zero, or a positive number depending on whether this is less than, equal to, or greater than other.

Many built-in types implement it: Integer, Double, String (lexicographic), LocalDate, etc.

Code Block
Java 8 (Update 492)

Define it for your own types when there is one obvious order that makes sense most of the time:

Code Block
Java 8 (Update 492)

Notice the use of Integer.compare(a, b) — never write a - b, which silently overflows for very large or very negative values.

The equals-consistency advisory

Whenever practical, compareTo should be consistent with equals: a.compareTo(b) == 0 iff a.equals(b).

When it is not consistent, sorted collections quietly do surprising things. For example, a TreeSet<BigDecimal> treats new BigDecimal("1.0") and new BigDecimal("1.00") as the same element (because compareTo returns 0 even though equals returns false). The Javadoc explicitly warns about this.

If your "natural order" is by, say, a single field, but two objects with the same field value should not be treated as equal, prefer a Comparator and leave Comparable out of it.

Comparator: an external order

A Comparator<T> is a small object that knows how to compare two Ts. Because it is external, you can have many of them for the same type:

Code Block
Java 8 (Update 492)

Comparator.comparing and comparingInt are key extractor factories: pass a function from T to a sortable key, get a Comparator<T> back.

Composing comparators

The real elegance of Comparator is composition. You can chain thenComparing to break ties:

Code Block
Java 8 (Update 492)

This is far more readable than a 10-line compareTo method that mixes three fields, and you can construct different orderings for different views without touching the entity class.

null-safe comparators

By default, comparators throw NullPointerException on null keys. Comparator.nullsFirst and nullsLast wrap any comparator into a null-tolerant one:

Code Block
Java 8 (Update 492)

Sorting in place vs. producing a sorted copy

NeedUse
Sort an existing List in placelist.sort(cmp) or Collections.sort(list, cmp)
Get a sorted copy of any Collectionlist.stream().sorted(cmp).toList()
Sort an arrayArrays.sort(arr, cmp)
Keep a continuously sorted viewTreeSet / TreeMap, or PriorityQueue for partial order

Multi-file example: sorting library users

Code Block
Java 8 (Update 492)

Naming each ordering as a method gives it intent — activeFirstThenOldestThenName() reads like the requirement. The class that owns the data does not have to know any of this.

Practice

Challenge
Java 8 (Update 492)
Top-3 by score

Implement Leaderboard.top3(List<Player>) returning the three players with the highest score, breaking ties by name ascending.

Expected output:

Bjarne 95
Linus 95
Grace 90

Test your understanding

QuestionSelect one

When should a class implement Comparable<T>?

Always, just in case

When there is one obvious natural order that is consistent with equals and serves most callers

Only when the class will be stored in a TreeSet

Whenever the class has numeric fields

QuestionSelect one

Why is return a - b; a bad implementation of compareTo for int values?

It is slower than Integer.compare

It can overflow when a and b are far apart in sign or magnitude, returning a wrong sign

It does not compile

It returns a value outside {-1, 0, 1}

QuestionSelect one

What does Comparator.comparing(X::getA).thenComparing(X::getB) do?

Sorts by b and then by a

Sorts by a ascending; uses b to break ties

Sums the two comparison results

Sorts only by a

On this page