Concepts / The Comparator Interface

The Comparator 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

A List That Puts Itself in Order

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.

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.

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 are the items in afterward?

Reveal answer
Answer:

apple, mango, pear

With a single argument, Collections.sort() puts the items into ascending order using their natural ordering. For Strings, that natural ordering is alphabetical, so apple comes first, then mango, then pear.

Why Sorting Belongs to Collections

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.

Definition

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.

Sorting With One Argument

The simplest form of the call takes one argument, the list. Its job is to sort the list into ascending order. Here is that form applied to the three words from the opening prediction.

java
Output
[apple, mango, pear]
Notes on the code
  • The list is created and filled in the order pear, apple, mango.
  • Collections.sort(fruits) is the single-argument form: only the list is passed in.
  • The list itself is rearranged; the Collections class does the work.

Trace what changed. Before the call, the list held the items in insertion order. After the call, the same list object holds them in ascending order. The printed output reflects that change, because the list was rearranged in place rather than copied into a new list.

What Ascending Order Means

The phrase ascending order means the smallest item comes first and the largest comes last, using the natural ordering of the items. For this 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 the wrapper classes such as Integer and Double. Those types already know how to compare themselves, so Collections.sort() can rely on that knowledge.

When the Natural Order Is Not Enough

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.

Notice the division of responsibility. The list is still the thing being rearranged. The items are still the things being compared. What changes is the source of the rule that decides which of two items belongs earlier.

Sorting Words by Length

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]
Walk through it
  1. 1
    Line 3 declares byLength as a Comparator for Strings. The anonymous class supplies the one method the interface requires.
  2. 2
    Line 5 defines compare(String a, String b). It receives two words from the list.
  3. 3
    Line 6 subtracts the lengths. For fig (3) and kiwi (4) the result is negative, so fig is placed before kiwi.
  4. 4
    Lines 9 to 12 build the list in its starting order: banana, fig, kiwi.
  5. 5
    Line 14 passes the list and the comparator to Collections.sort(). The comparator now governs every comparison the sort makes.
Notes on the code
  • byLength is a Comparator object whose compare() method holds the ordering rule.
  • compare(a, b) returns a negative number when a is shorter than b, so the shorter word is placed earlier.
  • Collections.sort(words, byLength) is the two-argument form: the list and the comparator are both passed in.
  • Alphabetical order would have produced [banana, fig, kiwi]; the comparator changed the result.

Reading the Return Value of compare()

Read the compare() method as the rule. It receives two words and returns a number. A negative result means the first word belongs earlier, a positive result means it belongs later, and zero means the two are treated as equal for ordering. 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 value of compare(a, b)Meaning for ascending orderResult for the list
Negativea belongs earlier than ba is placed before b
Zeroa and b are treated as equalneither is placed before the other by this rule
Positivea belongs later than ba is placed after b

The three possible results of a comparison, read as ascending-order decisions.

Where compare() Gets Called

The diagram below traces the calls during a two-argument sort. The caller hands the list and the comparator to Collections.sort(). Collections repeatedly asks the comparator to compare pairs of items taken from the list, reads the sign of each returned value, and rearranges the list accordingly. The comparator never touches the list directly; it only answers questions about two items at a time.

CallerCollections.sortwords listbyLength comparatorsort(words, byLength)reads items to comparecompare(itemA, itemB)negative, zero, or positiverearranges items
During a sort with a comparator, which parts of the program are involved, and in what order do they pass control and results to one another?

The sequence repeats: Collections picks pairs, the comparator returns a sign, and the list is rearranged, until the sort is finished. This is why the comparator's compare() method is the single place where the custom order is decided. Changing that method changes every placement the sort makes.

Choosing Between the Two Forms

The choice between the two forms follows one question: does the ordering come from the items, or from a rule you supply? The flowchart below shows the decision path. With one argument the ordering comes from the items, which must implement Comparable. With a second argument the ordering comes from the Comparator object that Collections.sort() is given. In both cases the list is the thing being rearranged.

Collections.sort(list)Comparator passed?Use items' ComparableorderingList in ascendingorderUse compare() of thecomparatornoyes
When Collections.sort() is called, which rule decides the order, and where does the decision branch?

Comparable and Comparator Side by Side

The names Comparable and Comparator are easy to confuse. The table below separates them by where the ordering logic lives and how each is supplied to sort().

AspectComparableComparator
Where the rule livesInside the item's classIn a separate object outside the items
How it reaches sort()Implicitly, through Collections.sort(list)Explicitly, as the second argument
Typical useThe natural ordering of Strings and wrapper classesA custom order such as word length
Typical method used by sort()The item's own comparisoncompare() on the supplied object

Comparable supplies the natural order; Comparator supplies an order chosen by the caller.

Mistakes When Sorting

  • Common mistake

    Calling the list's own sort method

    Why it fails

    The source explains that the List interface does not provide a sort method. Sorting is a static method of java.util.Collections.

    Do this instead
    Pass the list into Collections.sort(list) or Collections.sort(list, comparator).
  • Common mistake

    Expecting a single-argument sort on objects that are not Comparable

    What it looks like

    A list of a custom class whose items do not implement Comparable, sorted with Collections.sort(list).

    Why it fails

    The single-argument form relies on the items' natural ordering, which exists only when the items implement Comparable.

    Do this instead

    Make the items Comparable, or pass a Comparator as the second argument.

  • Common mistake

    Assuming a comparator is ignored when it is passed

    Why it fails

    A comparator replaces the items' natural ordering for that sort.

    Do this instead

    Remember that the comparator's compare() method decides the order, even for Strings that would otherwise sort alphabetically.

  • Common mistake

    Reversing the meaning of the compare() sign

    What it looks like

    Returning a positive value for a shorter word when the goal is shorter-first.

    Why it fails

    A negative result means the first argument belongs earlier, so the sign controls direction.

    Do this instead

    Return a negative value when the first word should come first, such as a.length() - b.length() when shorter words should come first.

Checking an Unexpected Order

When a sort produces an unexpected order, you can work backward through the questions this article has raised. Work through three checkpoints in order. First, identify which form of the call was used: the single-argument form or the two-argument form with a comparator. Second, if the single-argument form was used, check whether the items implement Comparable. Third, if a comparator was used, check what its compare() method returns for pairs of items. The divergence usually appears at one of these three checkpoints, because each one controls a different part of the ordering.

  1. 1
    Identify the form: one argument, or a list plus a comparator.
  2. 2
    For one argument, confirm the items implement Comparable.
  3. 3
    For a comparator, read compare() and check the sign it returns for a pair of items.
  4. 4
    Compare the result to the expected order and locate the first checkpoint where the two disagree.

Practice Changing the Order

MEDIUM

Using the words banana, fig, and kiwi, write a comparator that orders them from longest to shortest. Predict the output before you run the sort, then check your prediction by printing the list after Collections.sort(words, yourComparator).

Hints
  • Start from the byLength example and reverse which word is considered first.
  • Think about which sign the result should have when the first word is longer.
  • Predicted output for longest to shortest: [banana, kiwi, fig].

Key Takeaways

  • Collections.sort() is a static method of java.util.Collections, and a single-argument call sorts a list into ascending order.
  • The single-argument form works when the items implement Comparable, 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 compare() result places the first item earlier, a positive result places it later, and zero treats the two as equal for ordering.
  • When a sort is wrong, check the form of the call, whether the items are Comparable, and what the comparator returns.