Concepts / The Comparable Interface

The Comparable Interface

Collections.sort() is a static method of the java.util.Collections class, and it sorts a list into ascending order when called with a single argument.

  • Programming

Predicting a Sort Before Reading the Rules

Imagine a program that builds a list of words, asks Java to put them in order, and then prints the list. Sorting looks like a single step, but it touches several parts of the language at once: the list, the items inside it, and a utility method that does the work. This article approaches the topic from the outside in. You will first see what happens when a list is sorted, then learn which rules govern the order, and finally learn how to change that order with a Comparator.

What do you think happens?

Suppose a list contains three Strings, added in the order pear, apple, mango, and then Collections.sort() is called on it with a single argument. What order will the items be in afterward?

Reveal answer
Answer:

apple, mango, pear

A single-argument Collections.sort() puts the list into ascending order using the natural ordering of the items. For Strings, that natural ordering is alphabetical, so apple comes first, mango second, and pear last.

As you read, keep one question in mind: when the sorted output is not what you expected, where in the chain of steps did the result diverge? Learning to answer that question is the real skill this article builds.

A Sort Call Without a Sort Method

A natural first guess is that the List interface would provide a sort method, since sorting a list seems like a basic operation. It does not. The likely reason is that sorting only makes sense for certain kinds of objects, so the designers of the interface left it out. Instead, the sorting methods are static methods of a separate class, java.util.Collections.

Sorting a list is therefore a call on the Collections class rather than a method you invoke on the list itself. You pass the list in as an argument. If you have used the Arrays class to sort arrays, the idea is similar: a helper class supplies the operation, and you supply the data.

Collections.sort() is a static method of the java.util.Collections class. With a single argument, the list, it sorts the list into ascending order. The list is the thing being rearranged; the Collections class is the helper that performs the work.

Natural Ordering and the Comparable Rule

Definition

Ascending order means the smallest item comes first and the largest comes last, using the natural ordering of the items. For the single-argument call to work, the items in the list must implement the Comparable interface.

The source material gives two families of examples where this holds: Strings, and wrapper classes such as Integer and Double. Those types already know how to compare themselves, so Collections.sort() can rely on that knowledge. A Comparable item carries its own ordering rule, which is why the single-argument form needs nothing else from you.

Tracing a Single-Argument Sort

Here is the single-argument form in action. The list starts with pear, apple, and mango. After the call, the same list object holds the items in ascending order.

java
Output
[apple, mango, pear]
Notes on the code
  • List.of creates the starting list with the three Strings in the order pear, apple, mango
  • Collections.sort(fruits) is the single-argument form; it passes only the list
  • The printed output shows the list after sorting, in ascending order

The before row shows the list as it was added, and the after row shows the same three positions once the sort finishes. Each position now holds a different word. The list object itself was rearranged, which is why printing it afterward shows the sorted order.

Changing the Order with a Comparator

Sometimes the natural ordering is not the one you want, or the items do not implement Comparable at all. For those cases there is a second form of the call that takes a Comparator as its second argument. When a comparator is supplied, Collections.sort() uses it to compare items in the list, instead of relying on the items' own natural ordering.

The comparator is a separate object, so the ordering rule lives outside the items. This is what makes the second form useful for custom orders. The same items can be sorted in different ways by passing different comparators, without changing the items themselves.

Consider a list of words that should be ordered by how many characters each word has, rather than alphabetically. A Comparator can express that rule. The example below is an original illustration written for this article, not taken from the source. It defines a comparator whose compare() method compares the lengths of two Strings.

java
Output
[fig, kiwi, banana]
Notes on the code
  • The Comparator object holds the ordering rule, separate from the Strings
  • compare() receives two words and returns a number
  • Collections.sort(words, byLength) is the two-argument form; the comparator replaces natural ordering

Reading the Return Value of compare()

Read the compare() method as the rule. It receives two words and returns a number. The sign of that number tells the sort how the two items relate. Here the rule says that a shorter word comes before a longer one. Collections.sort() calls this rule on pairs of items from the list, so the rule, not the items' natural ordering, decides the final arrangement.

Return valueMeaning for the sortExample with byLength
NegativeThe first item belongs earlierfig compared with banana gives 3 minus 6, which is negative, so fig goes first
ZeroThe two items are treated as equal for orderingTwo words of the same length give 0
PositiveThe first item belongs laterbanana compared with fig gives 6 minus 3, which is positive, so banana goes later

How the sign of the value returned by compare() guides the sort

The sequence diagram shows the two-argument route. The calling code makes one call to Collections.sort(). The sort then repeatedly asks the comparator to compare pairs of items, and it uses each returned sign to decide the arrangement. The comparator's compare() method is therefore the place where the ordering rule is applied, while Collections.sort() handles the rearranging.

Mistakes That Break the Sort

  • Common mistake

    Calling the single-argument form on a list of objects that do not implement Comparable

    What it looks like

    Collections.sort(listOfCustomObjects) where the class never implements Comparable

    Why it fails

    The single-argument form relies on each item's natural ordering, and an item without Comparable has none to supply.

    Do this instead

    Make the class implement Comparable, or pass a Comparator as the second argument to supply the rule.

  • Common mistake

    Expecting the comparator to be ignored when it is passed

    What it looks like

    Passing byLength but expecting alphabetical output

    Why it fails

    When a comparator is supplied, Collections.sort() uses it instead of the items' natural ordering.

    Do this instead

    Check which form of the call was used, because the second argument decides the ordering rule.

  • Common mistake

    Assigning the result of Collections.sort() to a new variable

    What it looks like
    List<String> sorted = Collections.sort(words);
    Why it fails

    Collections.sort() rearranges the list passed to it and does not return a new sorted list to store.

    Do this instead

    Call Collections.sort(words) and then use words, which now holds the sorted order.

  • Common mistake

    Thinking the List interface has its own sort method, when sorting is a static call on Collections

Diagnosing an Unexpected Order

MEDIUM

A developer writes Collections.sort(people) on a list of Person objects. The class Person does not implement Comparable, and the developer expects the list to end up sorted by surname. Using the three-checkpoint method, identify which checkpoint fails and describe the two fixes that would make the call behave as intended.

Hints
  • Start by asking which form of the call was used
  • Ask whether the items supply a natural ordering
  • Consider whether a comparator that compares surnames could be passed as the second argument
EASY

Write a comparator that orders the words fig, banana, and kiwi from longest to shortest, then predict the printed output before running the code. Explain which sign of the return value puts the longer word first.

Hints
  • Reverse the subtraction used in the byLength example
  • A negative return value means the first item belongs earlier

Key Takeaways

  • Collections.sort() is a static method of the java.util.Collections class, and with a single argument it sorts a list into ascending order.
  • The single-argument form works when the items implement the Comparable interface, as Strings and wrapper classes such as Integer and Double do.
  • Passing a Comparator as the second argument supplies the ordering rule through its compare() method, replacing the items' natural ordering.
  • A negative, zero, or positive return value from compare() tells the sort whether the first item belongs earlier, the two are equal for ordering, or the first item belongs later.
  • When a sort result is wrong, check which form of the call was used, whether the items are Comparable, and what the comparator returns.