Comparable and compareTo: the Natural Order
Where you are: Week 0 review > Comparable and compareTo
Try This First
A compareTo returns -25. A teammate checks if (result == -1) to decide “comes first,” and the sort behaves strangely. Name the bug before reading on.
Reveal
compareTo communicates order by sign, not magnitude. Any negative value means “comes first,” so -25 is correct, but result == -1 misses it. Test result < 0, never result == -1.
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 Writing a Class: Fields, Constructors, and Encapsulation if any box above is unclear.
Not sure? Take the 60-second self-check.
Try each from memory, then read the answer under it.
- What two parts make up a simple class you write? Fields that hold its data and methods that act on that data.
- What sets an object’s fields when it is created? The constructor.
What You Need To Walk In With
Walk into the next class able to state these:
Comparable<T>gives a class one natural order through a single method,int compareTo(T other).- The return value carries order in its sign only: negative means
thiscomes first, zero means tied, positive meansthiscomes after. Magnitude means nothing, so test< 0and> 0, never== -1. - Compare numeric fields with
Integer.compareorDouble.compare, not by subtracting, which can overflow and give the wrong sign. - For more than one key, use the cascade: compare the first key, return it if non-zero, otherwise fall through to the next.
You should be able to: write a correct single-key and multi-key compareTo.
How It Works
public interface Comparable<T> {
int compareTo(T other);
}
| Return | Meaning |
|---|---|
| negative | this sorts before other |
| zero | tied |
| positive | this sorts after other |
A multi-key compareTo (GPA descending, then name, then a unique id) uses the cascade pattern:
@Override
public int compareTo(Student other) {
int byGpa = Double.compare(other.gpa, this.gpa); // descending: other before this
if (byGpa != 0) { return byGpa; }
int byName = this.name.compareTo(other.name); // ascending
if (byName != 0) { return byName; }
return Integer.compare(this.id, other.id); // unique key makes the order total
}
Each comparison is tried in order; the method returns the moment one is non-zero. The next key is reached only on a tie. A final unique key (the id) guarantees no two distinct students tie, so the order is total.
Use Integer.compare(a, b) rather than a - b. Subtraction can overflow for large or far-apart values and return a wrong-signed result; Integer.compare and Double.compare never do.
Worked Example: Predict, Then Check
Students sorted by the compareTo above: Alice (3.5), Bob (3.8), Carol (3.5). Predict the order.
Reveal
Bob (3.8), then Alice (3.5), then Carol (3.5). GPA descending puts Bob first; Alice and Carol tie on GPA, so the name tiebreaker orders Alice before Carol.
A Common Mistake
Writing return this.age - other.age; looks like a clean compareTo, but subtraction can overflow (for example, a large positive minus a large negative) and flip the sign, which corrupts the sort. Integer.compare(this.age, other.age) is always correct. Likewise, test the result with < 0, not == -1. (Source: BJP (Reges and Stepp), Ch 10; Effective Java, Item 14.)
Go Deeper (optional)
For the curious: the sign-only contract is what makes one compareTo usable by every sort, every TreeSet, and every TreeMap. They never look at the magnitude, only the sign, so any consistent negative, zero, positive scheme works. The contract also requires consistency (if a is before b and b is before c, then a is before c), which is why a unique final tiebreaker is worth adding.
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
compareTo returns -25. What does that mean?
Why use Integer.compare(a, b) instead of a - b in compareTo?
In a multi-key compareTo, when is the second key compared?
How should you test a compareTo result for ‘comes before’?