skip to content

In an equals() implementation, what is the trade-off between using getClass() and instanceof for the type check?

level: seniorimportance: should knowfreq 55%

answer

  1. getClass = exact class; instanceof = type or subtype
  2. getClass can break Liskov (subclass never equals parent)
  3. instanceof can break symmetry/transitivity if subclass adds fields
  4. Effective Java: instanceof + final class, or favour composition
  5. instanceof folds in the null check

basics

~20 s

getClass() requires the exact same class, so a subclass is never equal to its parent. instanceof allows subclasses to be equal. instanceof is more flexible but can make equality non-symmetric; getClass() is stricter but a subclass can never equal a parent.

solid answer

~50 s

The type check decides which objects can ever be equal. With getClass() != obj.getClass(), only objects of the exact same runtime class compare equal, so a Point and a ColorPoint are never equal. This preserves symmetry and transitivity automatically but violates the Liskov substitution principle: a subclass instance can never equal a superclass instance, even when that would be reasonable. With obj instanceof MyType, subclasses are accepted. That is friendlier to inheritance, but if a subclass adds a field to equality you can break symmetry — superPoint.equals(colorPoint) may return true while colorPoint.equals(superPoint) returns false. The classic guidance from Effective Java: prefer instanceof but make the class final, or favour composition over extension for value types. getClass() is the safe default when subclasses must not be considered equal. instanceof also returns false for null, so it folds the null check in.

go deeper

for a junior

Knows both idioms exist and that getClass() is the exact class while instanceof allows subclasses.

for a middle

Can state that instanceof may break symmetry and getClass() may break Liskov substitution, and that instanceof already excludes null.

for a senior

Explains the concrete symmetry/transitivity failure with a Point/ColorPoint example and recommends getClass() by default, or instanceof with a final class.

for a principal

Frames it as a value-type design decision, prescribes composition over inheritance for value objects, and sets a team convention (records, generated equals) that avoids the hazard entirely.

## The problem In `equals(Object obj)` you must verify the argument's type before casting. Two idioms exist, and the choice has subtle correctness consequences when **inheritance** is involved. ```java // Idiom A — exact class if (obj == null || getClass() != obj.getClass()) return false; // Idiom B — type-or-subtype if (!(obj instanceof Point)) return false; ``` ## What each one means `getClass()` returns the **exact runtime class** of an object. `a.getClass() == b.getClass()` is true only if both are *precisely* the same class — a `ColorPoint` is not equal-eligible with a plain `Point`. `instanceof` is true for the named type **and all its subclasses**. So `colorPoint instanceof Point` is true. ## The Liskov problem with getClass() The Liskov Substitution Principle says a subclass should be usable wherever its superclass is. With getClass(), a `Point(1,2)` and a `ColorPoint(1,2,RED)` are never equal — not even "the point part matches". If your design treats a ColorPoint as a kind of Point that should equal a Point with the same coordinates, getClass() forbids it. So getClass() can violate substitutability. ## The symmetry problem with instanceof Symmetry requires `a.equals(b) == b.equals(a)`. Suppose `Point.equals` uses `instanceof Point` and compares x,y, while `ColorPoint.equals` uses `instanceof ColorPoint` and also compares colour. Then: - `point.equals(colorPoint)` → point's equals sees a Point (instanceof passes), compares only x,y → **true**. - `colorPoint.equals(point)` → colorPoint's equals checks colour, but a plain Point has no colour → **false**. Symmetry is broken. Worse, you can engineer **transitivity** violations among three ColorPoints/Points. A naive "mixed" fix (ignore colour when comparing against a plain Point) breaks transitivity instead. **There is no way to extend an instantiable value class with a new value-significant field and preserve the equals contract** under inheritance (Effective Java, Item 10). ## Practical guidance 1. **Default to getClass()** when subclasses should not be considered equal — it can never break symmetry or transitivity. 2. **Use instanceof** when you genuinely want subclass instances to be interchangeable for equality (e.g. several proxy subclasses that add no value-significant state). To stay safe, **make the class final**, so no troublesome subclass can exist. 3. **Favour composition over inheritance** for value types: instead of `ColorPoint extends Point`, give ColorPoint a `Point point` field and a separate `Color`. Now there is no inheritance-equality clash at all. 4. Note `instanceof` returns false for `null`, so Idiom B does not need a separate null check; Idiom A still needs `obj == null`. ## hashCode follows the same rule Whichever type policy you pick, hashCode must use the same significant fields so equal objects still hash equally.

  • Give a concrete way symmetry breaks with instanceof.
    If Point.equals uses instanceof Point comparing x,y, and ColorPoint also compares colour, then point.equals(colorPoint) is true (only x,y checked) but colorPoint.equals(point) is false (point has no colour). a.equals(b) != b.equals(a).
  • How does composition over inheritance dissolve the problem?
    Instead of ColorPoint extends Point, ColorPoint holds a Point field plus a Color. There is no superclass/subclass equality relationship to reconcile, so symmetry and transitivity are trivially preserved, and you can expose the point via an accessor.

saying these in an interview costs you the question

  • Claiming instanceof is always correct and getClass() is wrong, or vice versa
  • Using instanceof on a non-final class while a subclass adds value-significant fields (breaks symmetry)
  • Believing you can extend an instantiable value class with a new equality field and keep the contract
  • Forgetting instanceof already returns false for null and adding a redundant null check while claiming it is required

context