Comparable vs Comparator
Natural ordering with compareTo, custom orderings with Comparator.comparing, thenComparing and reversed.
Sorting a list of numbers or strings just works: Collections.sort(names). But how should Java sort a list of Employees? By name? Salary? Joining date? Java offers two tools for defining order:
Comparable: the class defines its own natural order (one, built in).Comparator: a separate object defining any order you like (as many as you need).
The comparison contract#
Both use the same convention. Comparing a with b returns an int:
Comparable: natural ordering#
Implement Comparable<T> and its single method compareTo:
Many JDK classes already implement Comparable: String (alphabetical), Integer and other wrappers (numeric), LocalDate (chronological), and enums (declaration order).
Never implement
compareTowith subtraction (return a - b;). It overflows for large values and silently gives the wrong sign. UseInteger.compare,Double.compareand friends.
Ideally compareTo is consistent with equals: a.compareTo(b) == 0 exactly when a.equals(b). TreeSet and TreeMap use compareTo to detect duplicates, so inconsistency causes surprising behaviour.
Comparator: orders defined outside the class#
A Comparator<T> is a separate object with a compare(a, b) method. Use it when:
- the class has no natural order, or you can't modify it;
- you need several different orders (by price, by rating, by name);
- you want an order different from the natural one (reverse, case-insensitive).
Comparator is a functional interface, so lambdas work:
Mouse and Cable both have rating 4.3. On the third line they appear as Cable, Mouse only because sort is stable: tied elements keep their previous order, which happened to be alphabetical from the earlier sort. The fourth line gets the same result on purpose: the explicit tie-breaker (price ascending) puts the cheaper Cable first no matter what order the list was in before. Don't rely on leftovers from earlier sorts; state your tie-breakers.
The Comparator toolkit#
Watch where reversed() goes#
reversed() applies to the entire chain to its left:
Handling nulls and strings#
Without nullsFirst/nullsLast, sorting a list containing null throws NullPointerException.
Where comparators are used#
list.sort(cmp)andCollections.sort(list, cmp).Arrays.sort(array, cmp)for object arrays.new TreeSet<>(cmp),new TreeMap<>(cmp),new PriorityQueue<>(cmp).Collections.max(coll, cmp)andmin.- Streams:
stream.sorted(cmp),max(cmp),min(cmp)(Streams lesson).
Comparable or Comparator?#
Give a class a natural order only if there is one obvious ordering (versions, dates, money amounts). For everything else, use comparators at the point of sorting.
Common mistakes#
- Using subtraction in
compareTo/compare. - Putting
reversed()at the end of a chain when you only meant to reverse one key. - Sorting lists containing
nullwithoutnullsFirst/nullsLast. - A
compareToinconsistent withequals, causing aTreeSetto silently drop "duplicates". - Writing raw
Comparatorwithout its type parameter, which loses type safety.
What's next#
Comparators define order. Hash-based collections need to know about equality. The next lesson explains equals() and hashCode(): the contract, how to implement them correctly, and what breaks if you don't.
Check your understanding
Quick quiz
1.What should
a.compareTo(b)return whenashould come beforeb?2.Why is
return this.age - other.age;a risky compareTo implementation?3.Which comparator sorts employees by department, then by salary from highest to lowest?
Finished reading?
Mark this lesson complete to track your progress.