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?
answer
- Reflexive, Symmetric, Transitive, Consistent, Non-null
- instanceof + subclass field => symmetry/transitivity break
- getClass() keeps the contract but breaks substitutability (+ proxies)
- Favor composition over inheritance; records are final & safe
- Consistent => no time/network/random in equals()
basics
~20 sequals() 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 sThe 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
Can list the five properties by name and knows equals(null) returns false.
Explains each property with an example and can describe the subclass/instanceof symmetry break and that records avoid it.
Weighs getClass() vs instanceof (substitutability vs Hibernate proxies), prescribes composition over inheritance, and flags consistency violations like network/time-dependent equals.
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)