Custom Orderings: Comparator

We want to rank the roster by GPA. We worked out that the ordering cannot come from Student, because Student already uses its one compareTo for ID. So instead of asking the type how to order itself, we can supply the ordering from outside, as a separate object.

The Comparator interface

Java represents an ordering supplied from outside with another interface, Comparator:

public interface Comparator<T> {
  int compare(T a, T b);
}

Compare this with Comparable:

  • Comparable: a.compareTo(b). An element compares itself to another. One ordering, built into the type.
  • Comparator: comparator.compare(a, b). A separate object compares the two elements passed to it. We can write as many orderings as we need, and none of them is part of the type.

The return value follows the same negative / zero / positive convention. The difference is where the ordering is defined: inside the element, or in a separate object.

A type can only implement Comparable<Student> once, so it has one order. We can write five different classes that implement Comparator<Student>.

A comparator for GPA

Let’s write one that orders students by GPA. It is a class that implements Comparator<Student>, in the same way Student implements Comparable<Student>:

public class GpaComparator implements Comparator<Student> {
  @Override
  public int compare(Student a, Student b) {
    return Double.compare(a.getGpa(), b.getGpa());
  }
}

Double.compare gives us the negative, zero, or positive result for the two GPAs. Reversing the two arguments would order from highest GPA to lowest, which is what you want for a ranking.

Notice that we did not touch Student. The GPA order is defined entirely in GpaComparator. The ordering is now a value we can hold in a variable and pass to a method. In the next section we pass it to selectionSort.