Always Override hashCode When Overriding equals
When overriding the equals method in a class, you must also override the hashCode method. Failing to do so violates the general contract for hashCode, causing the class to malfunction in hash-based collections like HashMap and HashSet. The contract, derived from the Object specification, states:
- Repeated calls to
hashCodeon an object during a single execution must return the same integer, provided no fields used inequalscomparisons are modified. The value need not be consistent across different executions. - If two objects are equal via
equals, theirhashCodecalls must yield the same integer. - Unequal objects (per
equals) need not produce distinct hash codes, but distinct codes can improve hash table performance.
The critical violation when skipping hashCode override is the second rule: equal objects must share equal hash codes. Without an override, Object’s default hashCode returns arbitrary values for distinct instances, evenif equals deems them equal. For example, using a ContactNumber class (with regionCode, exchangeCode, subscriberNumber fields) as a HashMap key fails:
Map<ContactNumber, String> directory = new HashMap<>();
directory.put(new ContactNumber(212, 555, 1234), "Alice");
String name = directory.get(new ContactNumber(212, 555, 1234)); // Returns null
Here, two equal ContactNumber instances have different hash codes (from Object’s default), so get searches a different bucket. Even if buckets collide, HashMap skips equality checks due to mismatched codes.
Writing a Valid hashCode Method
A trivial but invalid implementation returns a constant (e.g., 42), forcing all objects into one bucket and degrading hash tables to linked lists (quadratic time complexity). A good hash function distributes unequal instances uniformly across integers. Use this approach:
- Initialize an
intvariable (e.g.,hashResult) with the hash code of the first significant field (a field affectingequals). - For each remaining significant field:
a. Compute its hash code (
fieldHash):- Primitive: Use
Type.hashCode(field)(e.g.,Integer.hashCode(intVal)). - Object reference: Recursively call
hashCodeifequalsdoes; use 0 fornull. - Array: Treat each significant element as a separate field (use
Arrays.hashCodefor all-significant arrays). b. MergefieldHashintohashResult:hashResult = 31 * hashResult + fieldHash.
- Primitive: Use
- Return
hashResult.
The multiplier 31 (an odd prime) helps disperse codes; modern VMs optimize 31 * i to (i << 5) - i.
Example: ContactNumber hashCode
Applying the steps to ContactNumber:
@Override
public int hashCode() {
int hashResult = Short.hashCode(regionCode);
hashResult = 31 * hashResult + Short.hashCode(exchangeCode);
hashResult = 31 * hashResult + Short.hashCode(subscriberNumber);
return hashResult;
}
This ensures equal instances share codes and disperses unequal ones. For brevity, use Objects.hash (slower due to array/boxing overhead):
@Override
public int hashCode() {
return Objects.hash(subscriberNumber, exchangeCode, regionCode);
}
Caching Hash Codes
For immutable classes with expensive hash computations, cache the result. Initialize lazily (thread-safe with care):
private int cachedHash; // Defaults to 0
@Override
public int hashCode() {
if (cachedHash != 0) return cachedHash;
int hashResult = Short.hashCode(regionCode);
hashResult = 31 * hashResult + Short.hashCode(exchangeCode);
hashResult = 31 * hashResult + Short.hashCode(subscriberNumber);
cachedHash = hashResult;
return hashResult;
}
Best Practices
- Never exclude significant fields from hash computation (risks collisions).
- Avoid specifying exact hash code values (allows future improvements).
- Verify equal instances yield equal codes via unit tests.
Use tools like AutoValue or IDE generators to automate equals/hashCode implementations.