skip to content

Implementing equals & hashCode

Writing the pair correctly: the self- and null-checks, getClass versus instanceof (symmetry against Liskov substitution), field-by-field comparison, and the helpers Objects.equals and Objects.hash. Interviewers often ask about the 31-multiplier idiom and the float, double and array special cases.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

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

open as a page

How do you implement hashCode() by hand, and why is the multiplier 31 used in the classic formula?

level: middleimportance: should knowfreq 60%

basics

~20 s

Start with a number, then for each field do result = 31 * result + fieldHash. The 31 mixes the fields so different objects spread out across buckets. Today you usually just write Objects.hash(field1, field2, ...) instead of doing it by hand.

open as a page

How do you correctly compare and hash float, double, and array fields inside equals() and hashCode()?

level: seniorimportance: should knowfreq 45%

basics

~10 s

For float/double use Float.compare/Double.compare (or Float.hashCode/Double.hashCode), not ==, because of NaN and -0.0. For arrays use Arrays.equals and Arrays.hashCode, not == or the array's own methods, which only check identity.

open as a page

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

level: seniorimportance: should knowfreq 55%

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.

open as a page

Given Java records and IDE/Lombok generators, when should you still hand-write equals() and hashCode(), and what governs that decision?

level: principalimportance: nice to knowfreq 35%

basics

~20 s

Most of the time you should not hand-write them: use a record or a generator, which produce correct equals/hashCode from your fields. Hand-write only when you need custom equality, such as comparing a subset of fields, normalizing values, or handling array fields that records compare by identity.

open as a page