skip to content

What is the relationship between equals() and hashCode() in Java, and why must they be overridden together?

level: juniorimportance: must knowfreq 85%

answer

  1. Equal objects -> equal hash codes (one-way rule)
  2. hashCode picks the bucket, equals confirms the match
  3. Override one => override both, from the SAME fields
  4. Default equals = identity; default hashCode = identity
  5. Break it and HashMap.get returns null for a key you put in

basics

~10 s

If two objects are equal by equals(), they must return the same hashCode(). So whenever you override equals(), you must also override hashCode(), or hash-based collections like HashMap and HashSet will misbehave.

solid answer

~30 s

equals() defines logical equality; hashCode() returns an int bucket used by hash-based collections. The core contract: if a.equals(b) is true, then a.hashCode() must equal b.hashCode(). The reverse is not required (unequal objects may share a hash code, a collision). You must override them together because hash-based collections first locate a bucket by hashCode(), then confirm with equals(). If you override equals() but leave the default Object.hashCode() (identity-based), two logically equal objects can land in different buckets, so a HashSet would store duplicates and HashMap.get() would fail to find a key you just put in. IDEs and Objects.hash()/Objects.equals() make consistent implementation easy.

code

java · 20 lines
java
import java.util.Objects;

final class Point {
    private final int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }

    @Override public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Point p)) return false;
        return x == p.x && y == p.y;          // same fields...
    }
    @Override public int hashCode() {
        return Objects.hash(x, y);            // ...as hashCode
    }
}

// If hashCode() were omitted, this would print false:
var set = new java.util.HashSet<Point>();
set.add(new Point(1, 2));
System.out.println(set.contains(new Point(1, 2))); // true when both overridden

go deeper

for a junior

States the one-way rule (equal objects => equal hash codes) and knows you must override both together, typically via the IDE or Objects.hash().

for a middle

Explains the bucket-then-equals mechanism of hash collections and can demonstrate the HashMap.get()-returns-null bug from overriding only equals().

for a senior

Reasons about why the converse isn't required (collisions), insists equals and hashCode use the same fields, and reaches for records/Objects utilities; aware of mutability pitfalls.

for a principal

Frames it as an invariant the whole codebase relies on; sets conventions (records/value objects, generated methods, immutability for keys) and can discuss hash distribution quality and collection internals.

## The two methods Every Java class inherits two methods from `java.lang.Object`: - **`boolean equals(Object o)`** — tests whether two objects are *logically* equal. The default inherited implementation tests *reference identity* (`this == o`): an object equals only itself. - **`int hashCode()`** — returns a 32-bit integer summarizing the object. The default returns an identity-based number (historically derived from the object's memory address). **Logical equality** means "these represent the same value/entity" — e.g. two `Point(1,2)` objects are different objects in memory but logically equal. To express that, you *override* `equals()` to compare fields instead of identity. ## Why a hash code exists A **hash-based collection** (`HashMap`, `HashSet`, `Hashtable`, `ConcurrentHashMap`) stores entries in an array of **buckets**. To find where an object goes, it calls `hashCode()`, reduces that int to a bucket index, and places the object there. Lookup repeats the calculation to jump straight to the right bucket — that's what makes `get`/`contains` average **O(1)** instead of scanning everything (O(n)). Because many objects share few buckets, **collisions** (different objects, same bucket) are normal. Inside a bucket the collection uses **`equals()`** to tell entries apart. So the two methods work as a team: `hashCode()` picks the bucket, `equals()` confirms the exact match. ## The contract that ties them The `Object` Javadoc states the binding rule: > **If `a.equals(b)` is `true`, then `a.hashCode() == b.hashCode()` must be `true`.** The converse is **not** required: two unequal objects *may* have the same hash code (that's just a collision and is allowed). Equal hash codes do **not** imply equality. ## What breaks if you override only one **Override `equals()` but not `hashCode()`** (the classic bug): two logically-equal objects keep the default *identity* hash codes, which differ. In a `HashMap`, you `put(key1, v)`, then call `get(key2)` where `key2.equals(key1)`. `get` computes `key2`'s (different) hash, looks in the wrong bucket, finds nothing, and returns `null` — even though the entry is right there. A `HashSet` would store both as distinct elements, silently allowing "duplicates." **Override `hashCode()` but not `equals()`**: equality falls back to identity, so two value-equal objects are never considered equal; the hash code is then merely a (legal but useless) consistency you don't benefit from. The collection treats them as distinct. ## Doing it right Since Java 7, `java.util.Objects` makes both trivial and null-safe: - `Objects.equals(a, b)` — null-safe field comparison. - `Objects.hash(f1, f2, ...)` — combines fields into a hash code, using the *same* fields you compared in `equals()`. **Golden rule:** `equals()` and `hashCode()` must be computed from the **same set of fields**. If `equals()` uses fields A and B, `hashCode()` must too — otherwise the contract can break. Records (Java 16+) and Lombok's `@EqualsAndHashCode` generate both consistently for you, and every major IDE can generate them as a pair.

  • If two objects have the same hashCode(), are they necessarily equal?
    No. That's a collision, which is permitted. Only the forward direction holds: equal objects must share a hash code. The collection still calls equals() within the bucket to decide true equality.
  • What is the simplest correct way to implement both in modern Java?
    Use a record (Java 16+) which auto-generates both from the components, or use Objects.equals()/Objects.hash() over the same fields. IDEs generate them as a matched pair too.

saying these in an interview costs you the question

  • Claiming equal hashCode implies equal objects (it does not — collisions are legal)
  • Overriding equals() but forgetting hashCode()
  • Using different fields in equals() vs hashCode()
  • Thinking hashCode() must be unique per object
  • Saying hashCode() is only for performance and is optional with hash collections

context