liveBook · Manning

Comparable interface

As explained in 100 Java Mistakes and How to Avoid Them

Comparable is a Java interface whose implementation defines a natural ordering for instances through compareTo.

Comparable defines a natural order for Java objects through compareTo(). Its result is sign-based: negative means less than, zero equal in ordering, and positive greater than. Implementations need not return only -1, 0, or 1. section 7.9

A valid compareTo() is antisymmetric and transitive. It must return 0 when an object is compared with itself or another object equal in its ordering; when two comparisons return 0, they must have the same comparison sign against every third object. Returning -1 for two wrappers of the same boolean value falsely declares each smaller than itself. section 7.10

class BoolObj implements Comparable<BoolObj> {
  final boolean b;

  BoolObj(boolean b) { this.b = b; }

  @Override
  public int compareTo(BoolObj o) {
    return b && !o.b ? 1 : -1;
  }
}

This implementation gets opposite boolean values right but never returns 0 for equal values. Return `b == o.b ? 0 : b ? 1 : -1`, or delegate to `Boolean.compare(b, o.b)`, instead. section 7.10

Use comparison results and alternative orderings defensively:

  • Test only the sign of a comparison result. `result == 1` is invalid because a conforming implementation may return any positive value; `String.compareTo()` can do so. section 7.9
  • Use a Comparator when the original class cannot be modified or when its natural order is unsuitable. Comparator.compare() follows a similar contract and can explicitly place null values with nullsFirst() or nullsLast(). section 7.10
  • Do not assume a content-based compareTo() matches equals() and hashCode(). StringBuilder compares content but retains identity-based equality and hashing, creating risky behavior as a mutable HashSet or HashMap key. section 8.6

Ordering users without assuming exact values

A User natural order can compare names first and ages only when names compare as zero. Comparing Mary, 30 with Mary, 20 may yield 1 through Integer.compare(), while comparing Mary with Joe can yield 3 through String.compareTo(). Both results mean greater than; code that expects exactly 1 can silently process data in the wrong order. section 7.9

Mutable keys are hazardous independently of ordering, but inconsistent comparison can deepen the problem. Since Java 8, heavily collided HashMap or HashSet buckets may use red-black trees and rely on compareTo(); mutating a StringBuilder after insertion can make contains() fail in a demonstrated collision scenario. section 8.6

How 100 Java Mistakes and How to Avoid Them covers it

How deep it goes
Details sign-based compareTo rules and focused failures, while limiting collection internals to a collision-dependent HashSet case.
Examples
Uses boolean wrappers, a name-and-age User record, and mutable StringBuilder values stored in HashSet.
What it assumes
Assumes Java object comparison and collections knowledge while defining the required compareTo and Comparator contracts.
What you can do after
Diagnose/debug comparison code by checking signs, equality results, and unsafe mutable or inconsistent collection keys.
How it connects
Relates Comparable to Comparator, compare(), String.compareTo(), equals(), hashCode(), HashMap, and HashSet.

Also explained in

Compare how 3 books explain Comparable interface →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Java Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.