The equals() & hashCode() contract
Why you must override both, the contract clauses, and the consequence of getting it wrong in a hash container.
Why hash containers need both methods
HashMap, HashSet, and friends find an element in three steps: (1) call hashCode() to pick a bucket, (2) within that bucket compare candidates with equals(). If you override only one of the two methods, those steps disagree and the container loses your objects.
The default Object.equals is identity (==, same reference) and the default Object.hashCode is derived from the object's identity. So two *logically equal* objects you created separately are, by default, unequal and have different hash codes. For a value class (a class whose identity is its contents — a Point, a Money, a SkillCode) you almost always want logical equality, which means overriding both.
### The contract (you must obey all of it)
equals must be reflexive (x.equals(x)), symmetric (x.equals(y) ⇔ y.equals(x)), transitive, consistent (same result on repeated calls), and x.equals(null) must be false.
The binding rule linking the two methods:
- If a.equals(b) is true, then a.hashCode() == b.hashCode() MUST be true.
- The converse is not required: two unequal objects *may* share a hash code (a *collision*) — that is allowed and merely costs a little speed.
So: equal objects must have equal hash codes. Violate this and a HashSet may store two objects you consider equal, or fail to find an object you just inserted.
Worked example. A value class Pair overriding both, using the same fields in each, with Objects.equals (null-safe) and Objects.hash:
class Pair {
final String key; final int value;
Pair(String key, int value) { this.key = key; this.value = value; }
@Override public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Pair)) return false;
Pair p = (Pair) o;
return value == p.value && Objects.equals(key, p.key);
}
@Override public int hashCode() {
return Objects.hash(key, value);
}
}
Now new Pair("a", 1).equals(new Pair("a", 1)) is true and both produce the same hashCode(), so a HashSet<Pair> correctly treats them as one element. Note the same fields (key, value) drive *both* methods — that is what keeps them consistent.