skip to content

State the five formal clauses of the equals() contract (as in Object's Javadoc) and what each requires.

level: middleimportance: must knowfreq 84%

answer

  1. Mnemonic: Reflexive, Symmetric, Transitive, Consistent, Null-false
  2. First three = an equivalence relation
  3. Symmetry breaks via cross-type equals
  4. Transitivity breaks via subclass adding a field
  5. instanceof handles null + wrong type at once

basics

~10 s

equals() must be: reflexive (x equals itself), symmetric (if x equals y then y equals x), transitive (x=y and y=z implies x=z), consistent (same result on repeated calls), and x.equals(null) must return false.

solid answer

~40 s

The contract from Object's Javadoc has five rules for non-null references. Reflexive: x.equals(x) is true. Symmetric: x.equals(y) is true if and only if y.equals(x) is true. Transitive: if x.equals(y) and y.equals(z), then x.equals(z). Consistent: repeated calls return the same result as long as no field used in equals changes. Non-nullity (the 'null returns false' rule): x.equals(null) must be false, never throw. These hold the equivalence-relation guarantees that hash-based and sorted collections rely on — break one and HashMap/HashSet can lose elements or return wrong results. Symmetry is most often broken by accepting a superclass or different type as equal; transitivity by adding fields in a subclass. A correct implementation also requires overriding hashCode consistently, though that is a separate contract.

code

java · 16 lines
java
// Symmetry violation: equals interoperating with String
final class CaseInsensitiveString {
    private final String s;
    CaseInsensitiveString(String s) { this.s = s; }

    @Override public boolean equals(Object o) {
        if (o instanceof CaseInsensitiveString c)
            return s.equalsIgnoreCase(c.s);
        if (o instanceof String str)             // <-- the bug
            return s.equalsIgnoreCase(str);
        return false;
    }
}
// cis.equals("Abc")  -> true
// "Abc".equals(cis)  -> false   (String.equals knows nothing of our type)
// => asymmetric. Fix: drop the String branch.

go deeper

for a junior

Can name reflexive/symmetric/transitive/consistent/null-false and that equals(null) returns false.

for a middle

Explains each clause with a concrete violation example (cross-type symmetry, subclass transitivity) and why instanceof covers null.

for a senior

Ties the equivalence-relation guarantee to collection correctness and articulates the 'no value-adding subclass' rule plus composition-over-inheritance fix.

for a principal

Discusses contract enforcement at scale: testing strategies (EqualsVerifier), API design to prevent foreign-type equals, immutability for consistency, and records/value-class adoption.

## What 'a contract' means here Java's `Object.equals(Object)` Javadoc specifies rules that any override **must** obey. They are not enforced by the compiler — they are a behavioral promise. Collections like `HashMap`, `HashSet`, and `TreeMap`, and library methods like `List.contains`, assume these rules hold. Violate them and you get silent, hard-to-debug bugs (lost entries, phantom duplicates). For all clauses, `x`, `y`, `z` are **non-null** references. ## The five clauses ### 1. Reflexive `x.equals(x)` must be **true**. An object is always equal to itself. Sounds trivial, but you can break it with exotic comparisons (e.g. NaN-style logic, or comparing a field that isn't stable). The practical guard: the first line of `equals` is often `if (this == o) return true;`. ### 2. Symmetric `x.equals(y)` must equal `y.equals(x)` — same boolean in both directions. The classic break: a class `CaseInsensitiveString` whose `equals` accepts a plain `String` argument. Then `cis.equals("abc")` might be true while `"abc".equals(cis)` is false, because `String.equals` doesn't know about your type. **Never make equals interoperate with a type that can't return the favor.** ### 3. Transitive If `x.equals(y)` and `y.equals(z)`, then `x.equals(z)`. The classic break is **subclassing that adds value-significant fields**: a `Point` and a `ColorPoint` that adds `color`. Any scheme where `Point` ignores color (so it can equal a `ColorPoint`) and `ColorPoint` checks color leads to a transitivity violation. This is the root of the maxim **"there is no way to extend an instantiable class and add a value component while preserving the equals contract"** — favor **composition over inheritance** here. ### 4. Consistent Multiple invocations of `x.equals(y)` must return the same result **provided no information used in equals comparisons changes**. Corollary: do not base `equals` on **unreliable / mutable external** resources (e.g. `URL.equals` famously does DNS resolution, making it non-deterministic and slow). Use only fields you control and that are stable. ### 5. Non-nullity ('null returns false') `x.equals(null)` must return **false** — and must **not** throw `NullPointerException`. This falls out naturally if you use `instanceof`, because `null instanceof AnyType` is `false`, so an early `if (!(o instanceof MyType)) return false;` handles both the wrong-type and the null case in one shot. ## Why an equivalence relation matters Reflexive + symmetric + transitive together make `equals` an **equivalence relation** — it partitions objects into clean equality classes. Hash-based collections store an object in the bucket for its `hashCode` and then use `equals` to confirm a match; if equality isn't a consistent equivalence relation, an object can fail to be found in the very bucket it was stored in, so `contains`/`get` lie. ## The hashCode corollary Not one of the five `equals` clauses, but inseparable in practice: **if you override `equals`, you must override `hashCode`** so that equal objects have equal hash codes. Otherwise hash collections break even when your `equals` is perfect. ## A canonical correct skeleton ```java @Override public boolean equals(Object o) { if (this == o) return true; // reflexive fast-path if (!(o instanceof Point)) return false; // handles null + wrong type (symmetry-safe) Point p = (Point) o; return p.x == x && p.y == y; // compare significant fields } ``` ## Takeaway Reflexive, Symmetric, Transitive, Consistent, null-false. They make equality an equivalence relation that collections trust; the usual offenders are cross-type equals (symmetry) and subclasses adding fields (transitivity).

  • Which two clauses are most commonly violated and by what coding pattern?
    Symmetry — by making equals accept a foreign/superclass type that can't reciprocate. Transitivity — by extending an instantiable class and adding a value component, so super and subclass disagree on which fields count.
  • Why does using instanceof in equals automatically satisfy the null clause?
    Because `null instanceof AnyType` evaluates to false, so the guard `if (!(o instanceof T)) return false;` returns false for null without a separate null check or any NPE.

Think of equals as a fair referee: it must agree with itself (reflexive), give the same call no matter which player asks (symmetric), be consistent across a chain of players (transitive), not change its mind on replay (consistent), and never call a 'nobody' (null) a valid player.

saying these in an interview costs you the question

  • Listing only three or four clauses and forgetting consistency or null-false.
  • Saying x.equals(null) should throw — it must return false.
  • Believing you can add a value field in a subclass and keep transitivity (you can't, generally).
  • Confusing 'consistent' (deterministic over time) with 'symmetric'.

context