skip to content

What are the five properties the equals() contract requires (reflexive, symmetric, transitive, consistent, non-null), and what is a common way to violate symmetry/transitivity?

level: middleimportance: must knowfreq 70%

answer

  1. Reflexive, Symmetric, Transitive, Consistent, Non-null
  2. instanceof + subclass field => symmetry/transitivity break
  3. getClass() keeps the contract but breaks substitutability (+ proxies)
  4. Favor composition over inheritance; records are final & safe
  5. Consistent => no time/network/random in equals()

basics

~20 s

equals() must be reflexive (x equals itself), symmetric (if x equals y then y equals x), transitive (if x=y and y=z then x=z), consistent (same result if nothing changes), and return false for null. Mixing a class with its subclass in equals() often breaks symmetry or transitivity.

solid answer

~50 s

The Object Javadoc requires equals() to be: reflexive (x.equals(x) is true); symmetric (x.equals(y) iff y.equals(x)); transitive (x=y and y=z implies x=z); consistent (repeated calls give the same result while objects are unchanged); and non-null (x.equals(null) is false). The classic violation appears when a subclass adds a field and tries to be equal to its superclass: if the parent uses an instanceof check, a Parent can equal a Child while the Child (comparing the extra field) does not equal the Parent, breaking symmetry — and chaining such comparisons breaks transitivity. The standard fixes are to use getClass() comparison (no instance of a different class is ever equal), or favor composition over inheritance for value classes, or use a record. You also must never let equals() depend on unreliable inputs like network state or system time, which would break consistency.

go deeper

for a junior

Can list the five properties by name and knows equals(null) returns false.

for a middle

Explains each property with an example and can describe the subclass/instanceof symmetry break and that records avoid it.

for a senior

Weighs getClass() vs instanceof (substitutability vs Hibernate proxies), prescribes composition over inheritance, and flags consistency violations like network/time-dependent equals.

for a principal

Sets org-wide policy (value types as records/finals, no equals-bearing inheritance hierarchies), and reasons about how contract violations cascade through collections and frameworks.

## What equals() promises `equals(Object)` defines an **equivalence relation** plus two extra rules. The `java.lang.Object` Javadoc spells out five properties; an implementation that violates any of them is *broken*, and library code (collections, `List.contains`, `Map`) may behave unpredictably. Let x, y, z be non-null references. 1. **Reflexive** — `x.equals(x)` must be `true`. An object always equals itself. (Almost impossible to get wrong, but e.g. NaN-style comparisons can.) 2. **Symmetric** — `x.equals(y)` returns `true` **iff** `y.equals(x)` returns `true`. The order of comparison must not change the answer. 3. **Transitive** — if `x.equals(y)` and `y.equals(z)` are both `true`, then `x.equals(z)` must be `true`. 4. **Consistent** — multiple invocations of `x.equals(y)` return the same result, *provided no information used in the comparison is modified*. So `equals()` must use only stable, in-memory state — never the current time, a random value, or a remote lookup. 5. **Non-null** — `x.equals(null)` must return `false` (never throw). The `instanceof` operator handles this for free, because `null instanceof Anything` is `false`. ## The classic symmetry break: superclass vs subclass Suppose `Point` overrides equals with an `instanceof` check on x and y. Now `ColorPoint extends Point` adds a `color` field. Two natural but incompatible choices: - If `ColorPoint.equals` requires the color to match but `Point.equals` ignores it, then for a plain `Point p` and a `ColorPoint cp` with the same coordinates: `p.equals(cp)` is **true** (Point ignores color, and `cp instanceof Point`), but `cp.equals(p)` is **false** (p has no color, fails the color check). That's a **symmetry violation**. - If you try to fix it by making `ColorPoint.equals` ignore color when compared to a plain `Point`, you can satisfy symmetry but then break **transitivity**: a red `ColorPoint`, a plain `Point`, and a blue `ColorPoint` can form a chain where the two endpoints are unequal even though each equals the middle. This is a fundamental tension: **there is no way to extend an instantiable class with a new value-defining field and preserve the equals contract while using `instanceof`.** ## Two correct strategies - **`getClass()` comparison:** require `o != null && o.getClass() == this.getClass()`. A `ColorPoint` is then *never* equal to a `Point`, which keeps the contract intact. Cost: it breaks the Liskov substitution principle for equality — a subclass instance can't be treated as equal to a superclass instance even when that would be desirable (and it interacts badly with Hibernate proxies, which subclass your entity). - **`instanceof` comparison:** more flexible and proxy-friendly, but **only safe when the class is effectively final** or the subclass adds no value-defining fields. Effective Java's advice: **favor composition over inheritance** — give `ColorPoint` a `Point` field and a view method instead of subclassing. **Records** (Java 16+) sidestep the whole problem: they are final and auto-generate a correct, field-based equals/hashCode. ## Why consistency deserves special care Consistency forbids equals() from depending on **unreliable resources**. A famous JDK example: `java.net.URL.equals` resolves host names to IP addresses, so two URLs can be equal or not depending on the network — a documented mistake. Keep equals() a pure function of immutable in-object state.

  • Why can't you both extend an instantiable class with a value-defining field and preserve the equals contract?
    Because the new field must matter for equality in the subclass but be invisible to the superclass; any instanceof-based scheme then breaks symmetry or transitivity. The accepted resolution is composition over inheritance (or a record).
  • When would you prefer getClass() over instanceof?
    When you want strict 'same exact type' equality and don't need a subclass to compare equal to its parent. Beware: it conflicts with frameworks like Hibernate that hand you proxy subclasses, where instanceof is usually preferred.

saying these in an interview costs you the question

  • Forgetting equals(null) must return false, or letting it throw NPE
  • Using instanceof while a subclass adds value-defining fields
  • Claiming you can extend a class with a new field and keep symmetry via instanceof
  • equals() that touches the network, clock, or random state
  • Confusing getClass() vs instanceof tradeoffs (substitutability vs proxies)

context