HashMap Key Equality in Java
Java `HashMap` uses a key's `hashCode()` to select a bucket and `equals()` to determine whether keys match. A key must keep equality- and hash-related behavior stable while it is stored in the map.
When you insert or look up a key, `HashMap` first uses `hashCode()` to narrow the search to a bucket. It then uses `equals()` to distinguish keys that land in the same bucket. Therefore, both methods matter for correct key lookup.
The general contract requires that if two objects are equal according to `equals()`, they must return the same `hashCode()`. The reverse is not required: two unequal objects may have the same hash code, which is called a hash collision.
Keys should normally be immutable with respect to the fields used by `equals()` and `hashCode()`. If such a field changes after insertion, a later lookup may calculate a different bucket and fail to find the existing entry.
When defining a custom key class, override `equals()` and `hashCode()` together. Java records are useful for value-like keys because their generated equality and hashing are based on their components.
Key points
- `hashCode()` narrows the search to a bucket; `equals()` establishes key equality.
- Equal objects must have equal hash codes.
- Hash collisions are valid and are resolved using equality checks.
- Do not mutate fields used by `equals()` or `hashCode()` while a key is stored in the map.
- Override `equals()` and `hashCode()` together for custom value-based keys.
Interview questions
What happens if two different keys have the same hash code?
The keys experience a hash collision and can still both be stored in the map. The map uses equality checks among candidates in the relevant bucket to determine which key matches a lookup. Therefore, a good hash function improves distribution and performance but does not need to produce unique values. Correctness comes from the equality contract, not from hash-code uniqueness.
How does `HashMap` use `hashCode()` and `equals()` when looking up a key?
The map first obtains the key's hash code to identify the relevant bucket. It then examines candidate entries in that bucket and uses `equals()` to determine whether a stored key matches the requested key. This lets different keys coexist even when their hash codes collide. Correct behavior depends on the `equals()`/`hashCode()` contract.
What is the contract between `equals()` and `hashCode()`?
If two objects are equal according to `equals()`, they must return the same hash code. Unequal objects are allowed to have the same hash code because collisions are possible. A class that overrides `equals()` should therefore override `hashCode()` consistently. Otherwise, hash-based collections can fail to find logically equal keys.
Why is a mutable key dangerous in a `HashMap`?
If a field used by `equals()` or `hashCode()` changes after insertion, the key's computed hash may change. A subsequent lookup can therefore search a different bucket from the one containing the entry. The entry may still physically exist in the map but become effectively unreachable through normal lookup. Immutable key state avoids this problem.
How would you implement a custom Java class intended to be used as a `HashMap` key?
I would define logical equality using immutable state and implement `equals()` and `hashCode()` from the same fields. I would ensure that equal instances always produce the same hash code and preserve symmetry, transitivity, and consistency in `equals()`. I would avoid changing those fields after insertion into a hash-based collection. For simple value objects, I would also consider a Java record when its semantics fit the domain.