skip to content

Walk through how you implement equals() for a Java class. What checks do you perform, and in what order?

level: juniorimportance: must knowfreq 80%

answer

  1. this == obj, then null/type, then cast, then fields
  2. Objects.equals for null-safe object fields
  3. Float.compare / Double.compare, not ==
  4. Arrays.equals for array fields
  5. override hashCode whenever you override equals

basics

~20 s

First check if the other object is the same one (==). Then check it isn't null and is the right type. Then cast it and compare each field that defines equality, using Objects.equals for object fields so nulls are handled safely.

solid answer

~40 s

A correct equals() follows a standard skeleton. First, the self-check: if (this == obj) return true, a fast path for the same reference. Second, the null-and-type check: either obj == null || getClass() != obj.getClass() (exact class) or obj instanceof MyType (allows subtypes), returning false on failure. Third, cast obj to the concrete type. Fourth, compare each significant field: == for primitives, Float.compare/Double.compare for float and double, Arrays.equals for arrays, and Objects.equals(a, b) for object references so null fields don't throw NullPointerException. Combine the field comparisons with &&. The method must obey the equals contract (reflexive, symmetric, transitive, consistent, non-null), and whenever you override equals you must also override hashCode so the two stay consistent for hash-based collections.

code

java · 9 lines
java
@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null || getClass() != obj.getClass()) return false;
    Person other = (Person) obj;
    return age == other.age                       // primitive
        && Double.compare(height, other.height) == 0 // double
        && Objects.equals(name, other.name);      // null-safe object field
}

go deeper

for a junior

Can recite the four-step skeleton (self-check, null/type, cast, compare fields) and knows to override hashCode alongside equals.

for a middle

Uses Objects.equals for object fields and the right comparison for each field type (Float.compare, Arrays.equals), and signs the method @Override to catch the equals(Object) signature mistake.

for a senior

Articulates why each step exists, the null-safety and float edge cases, and ensures equals and hashCode use the identical field set; explains the contract obligations.

for a principal

Sets team conventions (e.g. records or a generator), reasons about whether identity-based equals is correct for entities vs value objects, and weighs getClass-vs-instanceof against the domain's inheritance model.

## What equals() is Every Java object inherits `boolean equals(Object obj)` from `java.lang.Object`. The default implementation is **reference identity** — it returns true only if `this` and `obj` are literally the same object in memory (the same as `==`). When you want *logical* equality — two different `Point` objects that both hold x=3, y=4 should be considered equal — you must **override** equals. ## The standard skeleton, step by step ```java @Override public boolean equals(Object obj) { if (this == obj) return true; // 1. self-check if (obj == null || getClass() != obj.getClass()) return false; // 2. null + type Point other = (Point) obj; // 3. cast return x == other.x && y == other.y; // 4. field comparison } ``` **Step 1 — self-check (`this == obj`).** A performance shortcut: if the argument is the very same object, it is trivially equal, so we return true immediately and skip the rest. This also guarantees *reflexivity* (an object equals itself). **Step 2 — null + type check.** The contract says `x.equals(null)` must return `false` and never throw. Checking `obj == null` covers that. The parameter type is `Object`, so `obj` could be anything; we must confirm it is the right type before casting, otherwise step 3 throws `ClassCastException`. Two ways to check type: - `getClass() != obj.getClass()` requires the *exact same class*. - `obj instanceof Point` accepts the type **or any subclass**. (The trade-offs between them are a topic of their own — see the follow-up. instanceof also returns false for null, so with instanceof the explicit null check is redundant.) **Step 3 — cast.** Now that the type is verified, cast `obj` down to the concrete type so we can read its fields. **Step 4 — field-by-field comparison.** Compare every field that participates in logical equality: - **Primitives** (`int`, `long`, `boolean`, `char`…): use `==`. - **`float` and `double`**: use `Float.compare(a, b) == 0` / `Double.compare(a, b) == 0`, *not* `==`. `==` gives wrong results for `NaN` (`NaN == NaN` is false) and for `+0.0`/`-0.0` (they are `==` but `Float.compare` treats them as different — matching the behaviour needed for hash collections). - **Object references**: use `Objects.equals(a, b)`. This static helper returns true if both are null, false if exactly one is null, and otherwise delegates to `a.equals(b)` — so it is **null-safe**. Writing `a.equals(b)` directly throws `NullPointerException` when `a` is null. - **Arrays**: use `Arrays.equals(a, b)` (or `Arrays.deepEquals` for nested arrays), because an array's own `equals` is identity-based. Combine the comparisons with `&&` so the method short-circuits and returns true only when all significant fields match. ## Why hashCode comes along The equals/hashCode contract requires that **equal objects have equal hash codes**. The moment you override equals you must also override hashCode, or hash-based collections (`HashMap`, `HashSet`) will misbehave — an object stored under one key becomes unfindable. The modern one-liner is `Objects.hash(field1, field2, …)`. ## The fields used by equals and hashCode must match Use the **same set of significant fields** in both methods. If equals compares x and y, hashCode must be derived from x and y. Mismatched field sets silently break hash lookups.

  • What is the difference between using getClass() and instanceof for the type check?
    getClass() requires the exact same runtime class, which keeps symmetry but can violate the Liskov substitution principle (a subclass instance never equals its superclass instance). instanceof accepts subclasses, which is more flexible but can break symmetry if a subclass adds fields to equality. A common safe rule: use getClass(), or use instanceof and make the class final.
  • Why use Objects.equals(a, b) instead of a.equals(b)?
    Objects.equals is null-safe: it returns true if both are null, false if exactly one is null, and otherwise calls a.equals(b). Calling a.equals(b) directly throws NullPointerException when a is null.

saying these in an interview costs you the question

  • Declaring equals(MyType obj) instead of equals(Object obj) — that overloads, it does not override, so collections never call it
  • Calling field.equals(other.field) directly and getting NPE on null fields instead of using Objects.equals
  • Using == to compare float/double fields
  • Overriding equals but forgetting hashCode
  • Comparing array fields with == (identity) instead of Arrays.equals

context