liveBook · Manning

Comparator contract

As explained in 100 Java Mistakes and How to Avoid Them

A comparator contract is the set of consistency and ordering requirements that a comparison method must satisfy.

A comparator contract requires comparison results to describe a consistent ordering. Breaking it can do more than place values slightly out of order: sorting may fail, and ordered collections can behave unexpectedly. Faulty comparators often pass small or simple tests because invariant checks are conditional rather than guaranteed. section 7.11

Do not compare numeric fields by subtraction. `return i - o.i` can overflow when values have opposite signs, producing an ordering that contradicts other comparisons. Return an explicit sign with conditional comparisons, or prefer `Integer.compare(i, o.i)`. The same risk applies when subtraction is used as a tie-breaker, such as for hash codes. section 7.11

IntObj x = new IntObj(2_000_000_000);
IntObj y = new IntObj(0);
IntObj z = new IntObj(-2_000_000_000);
System.out.println(x.compareTo(y) > 0);    #1
System.out.println(y.compareTo(z) > 0);    
System.out.println(x.compareTo(z) > 0);     #2

Large positive and negative values expose how subtraction can claim both intermediate comparisons are positive while the final comparison is not. section 7.11

Sorting may detect an invalid ordering and throw `IllegalArgumentException: Comparison method violates its general contract!`. OpenJDK TimSort may perform these checks, but a bad comparator can escape detection: small collections use a simpler path, and particular input sequences may never trigger the check. Tests therefore need varied values and realistic collection sizes. section 7.11

For multi-field objects, treat the object as an ordered sequence of keys: compare the first key, then compare the next only when the first is equal. This avoids special cases that return the same result in both directions. A secure-connection preference, for example, is one boolean key followed by the URL, rather than an unconditional `-1` whenever the left value is secure. section 7.13

Build safer comparisons with these rules:

  • Compare every ordering key in sequence, returning at the first nonzero result. section 7.13
  • Make null placement an explicit key decision; compare nullity before comparing a nullable value. section 7.13
  • Use `Comparator.comparing(...).thenComparing(...)` for field sequences and `nullsLast(naturalOrder())` when null names belong last. section 7.13
  • Delegate `compareTo` to one shared comparator when both forms of comparison are needed. section 7.13

Null must not erase later distinctions

A comparison that returns `0` whenever either `name` is null makes `User(null)` compare equal to both `User("Mary")` and `User("Bill")`, even though those two named users differ. Compare nullity first; only compare names when both names are non-null. section 7.13

How 100 Java Mistakes and How to Avoid Them covers it

How deep it goes
Works through overflow, invariant-triggered sorting failures, lexicographic keys, and null handling without detailing TimSort internals.
Examples
Uses overflowing integer fields, hash-code tie-breakers, secure connections, nullable users, and multi-field records.
What it assumes
Assumes familiarity with Java objects, `compareTo`, fields, and basic collection sorting.
What you can do after
Apply it via Java `Comparator` combinators to construct consistent multi-key and null-safe orderings.
How it connects
Connects `Comparable`, `Comparator`, TimSort, TreeSet, null handling, and lexicographic ordering.

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.