← Skill tree CS Skill Tree 0 CSCD211

Alternate Orders Are Comparator<T>

Textbook: BJP (Reges and Stepp)

Before You Start

Check each box you can do from memory. A box you cannot check yet is not a problem; it points you to a quick refresher, not a grade.

See Natural Order Is implements Comparable&lt;T&gt; and review interface implementation in CSCD 210 if any box above is unchecked.

Not sure? Take the 60-second self-check.

1. Natural order. A class declares implements Comparable<Player>. How many natural orders does Player have?

Check

Exactly one. A class can implement Comparable<T> for at most one T, so it can have at most one compareTo, which means at most one natural order.

2. Sort with a second argument. What is the second argument to Arrays.sort(players, ???)?

Check

A Comparator<Player> instance. The sort uses that comparator’s compare method to decide the order instead of the natural order from compareTo.

3. Interface method names. Comparable<T> has compareTo. What is the method name on Comparator<T>?

Check

compare. It takes two arguments of type T, not one. The receiver (this) is not one of the comparands.


Try This First

A Player class already implements Comparable<Player> (ordering by last name). You now need to sort an array of players by team name, then by games played. Before reading further, decide: should you (a) modify Player.compareTo, (b) add a method compareToByTeam(Player other) to Player, or (c) write a separate class?

Write your choice and a one-sentence justification.

What is the right answer?

Choice (c): write a separate Comparator<Player> class. Option (a) breaks the existing natural order; option (b) is not understood by Arrays.sort (it only sees compare from Comparator<T>). A separate Comparator<Player> class is passed at the call site without touching Player.java at all.


What You Need To Walk In With

A Comparator<T> lives outside the type it compares. The type being sorted does not change. You can write any number of comparators for the same type and choose among them at each call site. This is the key difference from Comparable<T>, where the comparison logic lives inside the class and there is only one of them.

The main thing to remember is that Comparator<T> requires two parameters (compare(T a, T b)) and lives in a separate class. Everything else follows from that: you can identify a comparator class in a directory listing by its name and package, and you can explain why alternate orderings belong there rather than as extra methods on the type itself. You can: name the method Comparator<T> requires and its exact signature, distinguish compare(T a, T b) from compareTo(T other) by counting parameters, predict what Arrays.sort(arr, comparatorInstance) does compared to Arrays.sort(arr), and choose whether a new ordering belongs in compareTo or in a new Comparator class.


How It Works

The Comparator interface at a glance

Comparator<T> is a one-method interface (ignoring default methods). Its single required method is:

int compare(T a, T b)

Returns a negative integer, zero, or a positive integer as a is less than, equal to, or greater than b in the ordering this comparator defines. (Java 25 Comparator docs)

The F19 Lab 1 example

This lab is the canonical demonstration of the Comparable/Comparator dual in this course. The Player type has a natural order (its own compareTo). The cscd211Comparators/ package adds three separate ordering objects for the same type:

cscd211Comparators/
    TeamComparator.java          // orders Player by team name
    PositionComparator.java      // orders Player by position
    GamesPlayedComparator.java   // orders Player by games played (ascending)

Player.java does not change. At the call site, the caller picks whichever ordering it needs:

// natural order: sort by last name
Arrays.sort(players);

// alternate order: sort by team
Arrays.sort(players, new TeamComparator());

// alternate order: sort by games played
Arrays.sort(players, new GamesPlayedComparator());

Each comparator class is entirely self-contained. Adding a fourth ordering (say, by salary) requires writing one new file and zero modifications to Player.java.

The signature difference

The two interfaces look similar but are not interchangeable:

Interface Method Parameters Who is the first comparand?
Comparable<T> compareTo(T other) one this (the receiver)
Comparator<T> compare(T a, T b) two the first explicit argument a

A Comparator<Player> class has no Player receiver in the comparison. Both Player objects arrive as explicit arguments.


Worked Example: Predict Then Check

The following excerpt is from a client class that has both sorting options available:

Player[] roster = { playerC, playerA, playerB };

// sort 1: natural order (last name)
Arrays.sort(roster);
System.out.println(Arrays.toString(roster));

// sort 2: by team name
Arrays.sort(roster, new TeamComparator());
System.out.println(Arrays.toString(roster));

Assume:

Predict what each println prints (just show the order of names).

Step-by-step reasoning

Sort 1 uses compareTo on last name: Adams (A) < Burns (B) < Chen (C), so the order is Adams, Burns, Chen.

Sort 2 uses TeamComparator, which delegates to String.compareTo on the team field: Aces (A) < Bears (B). Both Adams and Chen are on Bears; among them, TeamComparator sees equal team names and returns 0, so their relative order is determined by the sort algorithm’s stability (covered with sorting and collections).

Show answer

Sort 1 (natural order): [Adams, Burns, Chen]

Sort 2 (by team): Burns (Aces) first, then Adams and Chen (both Bears) in some order.

The key point: two entirely different orderings of the same array, produced by passing different comparator objects at the call site, with no change to Player.java.


Quick check

Check your understanding

After this client code runs, which method determined the order in the second sort? Player[] roster = { playerC, playerA, playerB }; Arrays.sort(roster); Arrays.sort(roster, new TeamComparator()); Choose the correct answer.

Tier 2 · BJP (Reges and Stepp), Ch 10


Common Misconceptions

Misconception 1: adding alternate orderings as extra methods on the type

Wrong mental model: “To sort by team I add compareToByTeam(Player other) on Player. To sort by games played I add compareToByGamesPlayed. Each ordering is just another method.”

Why it breaks: Arrays.sort(players, ???) expects a Comparator<Player> instance, not a method reference to compareToByTeam. Custom-named comparison methods are invisible to sort routines. The class fills with comparison methods that no standard API can call. Adding a fourth ordering later requires editing Player.java, recompiling everything that depends on it, and growing the class indefinitely.

How to correct: Each alternate ordering gets its own Comparator<Player> class. Player.java does not change. The caller passes the right comparator at the sort call site.

Source: Bloch, Effective Java, Item 14.

Quick check

Check your understanding

A Student class already implements Comparable<Student> (natural order by GPA). A professor needs to sort students by last name for a seating chart. Which approach correctly adds this ordering?

Tier 2 · BJP (Reges and Stepp), Ch 10


Misconception 2: confusing the compare and compareTo signatures

Wrong mental model:compare and compareTo are different names for the same operation, so I can use either signature in either context.”

Why it breaks on a small example: A Comparator<Player> written with one parameter:

public class PositionComparator implements Comparator<Player>
{
    @Override
    public int compare(final Player other)   // BUG: only one parameter
    {
        return this.getPosition().compareTo(other.getPosition());
    }
}

This does not compile: Comparator<Player> requires compare(Player a, Player b) with two parameters. The method above has the wrong signature and does not override anything. With @Override present, the compiler reports the error immediately.

How to correct: In a Comparator<T> class, always write compare(T a, T b) with two explicit parameters. In a Comparable<T> class, write compareTo(T other) with one parameter, comparing this to other.

Source: observation from nearly every quarter’s first Comparator lab.


Formal Definition and Interface Contract

From the Java 25 Comparator API:

A comparison function, which imposes a total ordering on some collection of objects. Comparators can be passed to a sort method (such as Collections.sort or Arrays.sort) to allow precise control over the sort order. Comparators can also be used to control the order of certain data structures (such as sorted sets or sorted maps), or to provide an ordering for collections of objects that don’t have a natural ordering.

int compare(T o1, T o2) -- Compares its two arguments for order. Returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.

The same sign contract applies: negative means a before b, zero means equal, positive means a after b. The contract requirements are identical to those of compareTo (antisymmetry, transitivity, consistency).


Mental Model

Think of a Comparator<T> as a judge brought in from outside. The type being judged (Player, Book, Widget) does not change; it just presents itself to the judge. You can bring in any number of judges, one for each criterion, and swap them at the call site. The natural order (compareTo) is the type judging itself: one built-in criterion, always available, never swappable.


Connections

Within CSCD 210/211: The compare(T a, T b) signature and the compareTo(T other) signature are both covered in this lane. Implementing Comparator&lt;T&gt; as a Named Class explains how to write the named Comparator class in full. One Class, Many Comparators by Design revisits the F19 Lab 1 design to show how multiple comparators coexist with a natural order.

Looking back: Natural Order Is implements Comparable&lt;T&gt; established what a natural order is and why a class can have at most one. This lesson is the other half of the same dual.

Looking ahead: Implementing Comparator&lt;T&gt; as a Named Class covers the full named-class form for writing a Comparator. Anonymous-Class Syntax for Comparator and Writing a Comparator as a Lambda replace the named class with an anonymous inner class and then a lambda. Comparator.comparing by Key replaces all of those with the composable Comparator.comparing API.


Practice

Level 1

A directory listing shows:

cscd211Comparators/
    GamesPlayedComparator.java
    PositionComparator.java
    TeamComparator.java

Without opening the files, answer: what interface does each class most likely declare to implement? What is the type parameter?

Thought process

All three are in a comparators package and are named for orderings of the Player type from F19 Lab 1. Comparator classes implement Comparator<T> where T is the type they compare.

Show answer

Each class implements Comparator<Player>. The package name (cscd211Comparators) and the naming convention (<Field>Comparator) both signal this. The type parameter <Player> is the type being ordered.

Source: F19.Lab1-Review.


Level 2

A Book class already implements Comparable<Book> (ordering by ISBN). You must add support for sorting books by page count (ascending). Identify which of these three approaches is correct, and explain why the other two are wrong:

(a) Modify Book.compareTo to order by page count.

(b) Add a method compareByPages(Book other) on Book.

(c) Write a new class BookByPagesComparator implements Comparator<Book>.

Thought process

Consider what each approach changes and what Arrays.sort can actually use.

Show answer

(c) is correct. Write BookByPagesComparator implements Comparator<Book> and pass it as the second argument to Arrays.sort.

  • (a) is wrong because it destroys the existing natural order (by ISBN). Any code that relied on Arrays.sort(books) with no second argument would now sort by pages.
  • (b) is wrong because Arrays.sort has no knowledge of a method named compareByPages. It only uses the compare method from Comparator<T> or the compareTo method from Comparable<T>.

Source: pattern from S20.Lab1-Comparator BookISBNComparator.


Level 3

A Player class implements Comparable<Player> (natural order: last name). Write a GamesPlayedDescendingComparator implements Comparator<Player> class that orders players by games played, most games first. Include all required coding-standard elements. Then explain in one sentence what would have to change in Player.java to support this ordering instead.

Thought process

For descending order, swap the arguments to Integer.compare. Then answer the second question: nothing would have to change in Player.java because the ordering is entirely in the comparator.

Show answer
package cscd211Comparators;

import java.util.Comparator;
import cscd211Classes.Player;

public class GamesPlayedDescendingComparator implements Comparator<Player>
{
    @Override
    public int compare(final Player a, final Player b)
    {
        if (a == null || b == null)
            throw new IllegalArgumentException(
                "Bad Player in GamesPlayedDescendingComparator.compare");
        return Integer.compare(b.getGamesPlayed(), a.getGamesPlayed());
    }
}

Player.java requires zero changes: the Comparator<Player> mechanism is designed so that alternate orderings live entirely outside the type being ordered.

Pattern from F19.Lab1-Review GamesPlayedComparator, inverted for descending.


Go Deeper (optional)

This section is for when you are curious, not for writing correct code on the lab. Everything you need to do the work is already above.

The Comparator<T> design has a precise mathematical meaning: a comparator instance is a binary relation that imposes a strict total order on its type. The three contract rules (antisymmetry, transitivity, totality) are exactly the axioms of a strict total order from discrete math and order theory. The reason those contract rules matter in practice is that sorting algorithms depend on them; a comparator that violates transitivity can produce an infinite loop or a garbled output from Arrays.sort.

The design pattern at work here is Strategy. The comparator object encapsulates one algorithm (comparison by team, comparison by games played) and makes it swappable at the call site. Parnas described this principle in 1972 under the heading of information hiding: each ordering decision is isolated behind its own module so that a change to one ordering cannot affect any other. The Strategy pattern is the object-oriented name for the same idea. When you later see lambdas and Comparator.comparing, you are still using Strategy; the named class is just replaced by a first-class function value, which is the functional-programming surface of the same concept.

The has-a vs. is-a distinction also applies: a type that implements Comparable says “I am an ordered thing,” while a type that implements Comparator says “I have a way to order two things of type T.” The comparator has an ordering; the comparable is an ordering. This is why a Comparator can be an object with no fields at all (its only job is to hold the comparison logic) while Comparable lives inside the type that owns the data.

In production Java code, almost every sort uses a Comparator rather than the natural order, because the sort criterion is context-specific: by date in one report, by revenue in another, by customer name for one export and by region code for another. Knowing when to reach for a Comparator rather than modifying compareTo is a routine decision in enterprise development.


Check Yourself

Close the notes and answer each one from memory, then reveal it. Pulling an idea back from memory is one of the strongest ways to make it stick.

Check your understanding

Which interface must a class implement to provide an alternate ordering for a type that already has a natural order?

Tier 1 · BJP (Reges and Stepp), Ch 10

What is the exact signature of the single required method on Comparator<T>?

Tier 1 · BJP (Reges and Stepp), Ch 10

Given this client code from the F19 Lab 1 example: Player[] roster = { playerC, playerA, playerB }; Arrays.sort(roster); Arrays.sort(roster, new TeamComparator()); After both sorts run, what does the second Arrays.sort use to determine order?

Tier 2 · BJP (Reges and Stepp), Ch 10

A Book class already implements Comparable<Book> (natural order by ISBN). A new requirement calls for sorting books by page count. Which approach is correct?

Tier 2 · BJP (Reges and Stepp), Ch 10

A student writes this Comparator class: public class PositionComparator implements Comparator<Player> { @Override public int compare(final Player other) { return this.getPosition().compareTo(other.getPosition()); } } What happens when this file is compiled?

Tier 3 · BJP (Reges and Stepp), Ch 10