What does it mean for compareTo to be consistent with equals, and when is it acceptable to break that consistency?
answer
- compareTo==0 iff equals==true
- Strongly recommended, NOT required
- TreeSet/TreeMap use compareTo, never equals
- BigDecimal 1.0 vs 1.00 is the example
- If broken, document it in Javadoc
basics
~10 sConsistent means x.compareTo(y) == 0 exactly when x.equals(y) is true. It is strongly recommended but not required. Breaking it is allowed if documented, but it can make sorted collections behave surprisingly.
solid answer
~50 sA class's natural ordering is consistent with equals when, for every x and y, x.compareTo(y) == 0 if and only if x.equals(y). The Comparable contract strongly recommends this but does not mandate it. The reason it matters: sorted collections like TreeSet and TreeMap decide equality using compareTo, not equals. So if two objects are equal by compareTo but not by equals, a TreeSet will treat them as the same element and reject the second as a duplicate, while a HashSet would keep both. The classic JDK example is BigDecimal: new BigDecimal("1.0").equals(new BigDecimal("1.00")) is false (different scale), but their compareTo returns 0, so a TreeSet of BigDecimals deduplicates them where a HashSet does not. Breaking consistency is legal if you document it clearly, but it surprises maintainers and violates the Set/Map general contracts, so prefer consistency unless you have a strong reason.
go deeper
Aware that compareTo returning 0 means equal-in-order and that this is related to but separate from equals.
States the iff rule (compareTo==0 iff equals) and knows it is recommended, not enforced.
Explains that sorted collections use compareTo not equals, cites the BigDecimal example, and knows when/how to document a deliberate break.
Weighs ordering-granularity vs equality-granularity as a deliberate API decision, anticipates the cross-collection surprises for downstream users, and sets team conventions for documenting inconsistency.
## The two notions of "same" Java has two independent ways to ask whether two objects are the same: 1. **`equals(Object)`** — logical equality. Returns `true`/`false`. 2. **`compareTo`** returning **0** — equality *in ordering*. Returns an `int` whose zero value means "neither comes first". These are separate methods and could, in principle, disagree. The **consistency-with-equals** rule is about keeping them in sync. ## The definition A class's natural ordering is **consistent with equals** when: > for all `x` and `y`, `x.compareTo(y) == 0` **if and only if** `x.equals(y)` is `true`. In plain words: two objects compare as ordering-equal *exactly* when they are `equals`-equal. The `Comparable` Javadoc **strongly recommends** this but explicitly says it is **not required** — it is a *should*, not a *must*. ## Why it matters: sorted collections use compareTo, not equals Here is the crux. The hash-based collections (`HashSet`, `HashMap`) decide membership/duplication using `equals` (and `hashCode`). But the **sorted** collections — `TreeSet`, `TreeMap`, and anything built on `SortedSet`/`SortedMap` — decide membership and duplication **purely by `compareTo`** (or a supplied `Comparator`). They **never call `equals`**. Consequence: if `compareTo` says two objects are equal (returns 0) but `equals` says they differ, then: - A `TreeSet` treats them as **the same element** — adding the second is a no-op (rejected as a duplicate). - A `HashSet` treats them as **different elements** — it keeps both. The same collection, conceptually a `Set`, now contains a different number of elements depending on which implementation you chose. That is the precise sense in which an inconsistent ordering "breaks" the general `Set`/`Map` contract: those contracts are defined in terms of `equals`, but `TreeSet`/`TreeMap` answer in terms of `compareTo`. ## The canonical JDK example: BigDecimal `BigDecimal` deliberately breaks consistency: ```java BigDecimal a = new BigDecimal("1.0"); BigDecimal b = new BigDecimal("1.00"); a.equals(b); // false -- scale differs (1 vs 2 decimal places) a.compareTo(b); // 0 -- numerically equal ``` `equals` considers **scale** (the number of fractional digits), so `1.0` and `1.00` are unequal objects; but `compareTo` is purely numeric, so they are ordering-equal. The fallout: ```java Set<BigDecimal> hash = new HashSet<>(List.of(a, b)); // size 2 (equals differs) Set<BigDecimal> tree = new TreeSet<>(List.of(a, b)); // size 1 (compareTo == 0) ``` This is the example interviewers expect you to name. ## When is breaking it acceptable? Because the rule is a recommendation, breaking it is **legal** when: - You have a genuine reason the ordering granularity differs from equality granularity (BigDecimal's numeric-vs-scale split is the textbook case). - You **document it explicitly** in the class Javadoc — the convention is to add a note such as: *"Note: this class has a natural ordering that is inconsistent with equals."* If you do break it, callers who put the type in a `TreeSet`/`TreeMap` must understand they are deduplicating by ordering, not by equality. ## Default and rule of thumb Most value types should keep ordering and equality aligned: compare exactly the fields you use in `equals`, in some priority order. Reach for inconsistency only deliberately, and flag it. When in doubt, make them consistent — it is the principle of least surprise, and it keeps `TreeSet`/`HashSet` interchangeable as `Set` implementations.
- Concretely, what goes wrong when you put inconsistent objects into a TreeSet?TreeSet decides duplicates by compareTo. Objects that compareTo as 0 but are not equals-equal get collapsed into one element, so the TreeSet silently drops entries a HashSet would keep — changing the set's apparent size.
- How would you signal to users that your class is intentionally inconsistent with equals?Document it in the class Javadoc with the conventional note 'this class has a natural ordering that is inconsistent with equals', mirroring how BigDecimal documents it.
saying these in an interview costs you the question
- Saying consistency with equals is mandatory/enforced by the compiler
- Believing TreeSet uses equals to detect duplicates (it uses compareTo)
- Not knowing the BigDecimal scale example
- Claiming HashSet and TreeSet always hold the same elements for the same type