All topics
library
beginner

equals() & hashCode() Contract

Understand the contract between equals and hashCode, and what breaks when you violate it.

HashMap finds a key in two steps: hashCode() picks the bucket, then equals() identifies the key inside it. Break either and lookups fail.

hashCode = a filing cabinet drawer number (tells you which drawer to look in). equals = comparing the actual document to confirm it's the right one. Wrong drawer number = you'll never find the document.

Key Concepts

1
    @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;
    }
2
    @Override
    public int hashCode() { return Objects.hash(x, y); }
3
The contract.
    a.equals(b)  =>  a.hashCode() == b.hashCode()      required
    different hash codes  =>  not equal                 follows
    equal hash codes  =>  may or may not be equal       collisions are fine
4
Override equals without hashCode and objects vanish.
    Set<Point> seen = new HashSet<>();
    seen.add(new Point(1, 2));
    seen.contains(new Point(1, 2));      // false
equalshashCode
5
The two points are equal, but Object.hashCode gave them different values, so contains looks in the wrong bucket and never calls equals. You can put entries in and never get them back.
Object.hashCodecontainsequals
6
What equals must satisfy.
    reflexive     x.equals(x) is true
    symmetric     x.equals(y) == y.equals(x)
    transitive    x=y and y=z implies x=z
    consistent    the answer does not change on its own
    null          x.equals(null) is always false
equals
7
Symmetry is the one subclasses break. If ColourPoint.equals requires the other object to be a ColourPoint, then point.equals(colourPoint) and colourPoint.equals(point) disagree — and a collection holding both behaves differently depending on insertion order.
ColourPoint.equalsColourPointpoint.equals(colourPoint)colourPoint.equals(point)
8
A bad hash code is a performance bug, not a correctness one.
    public int hashCode() { return 1; }      // legal, and ruinous
9
Every key lands in one bucket, so the map degrades to a list: O(n) lookups. Since Java 8 a long bucket becomes a tree, which softens it to O(log n) — still far off O(1).
10
Use only fields you compare in equals, and avoid mutable ones. A key whose hash changes while it is in a map is lost.
equals
11
What the interviewer is probing.1. "State the contract between equals and hashCode." Probing: whether you know it precisely. Stalls: "They should agree." Moves up: equal objects must have equal hash codes; unequal objects may collide. The reverse is not required, which is why collisions are legal.
12
2. "You add an object to a HashSet, then mutate a field used in equals. What happens?" Probing: mutable keys. Stalls: "It still finds it." Moves up: its hash changed, so contains looks in a different bucket and the object is effectively lost — which is why keys should be immutable.
13
3. "What is wrong with hashCode() returning 1?" Probing: whether you distinguish correctness from performance. Stalls: "It breaks the map." Moves up: it is legal and correct, but every key lands in one bucket, so lookups degrade to O(n) — softened to O(log n) since Java 8 when the bucket becomes a tree.
14
4. "How does subclassing break symmetry?" Probing: the hard part of the contract. Stalls: "It does not." Moves up: if ColourPoint.equals requires a ColourPoint, then point.equals(cp) and cp.equals(point) disagree, and collection behaviour depends on insertion order.