In Java, the hashCode() and equals() methods play a pivotal role in how objects behave in hash-based collections like HashMap, HashSet, and Hashtable. If you're a Java developer (or prepping for interviews), understanding this contract is non-negotiable.
Let’s break it all down with rules, rationale, examples, and best practices.
🧠 Why is the hashCode and equals Contract Important?
When objects are stored in hash-based collections:
hashCode() decides the bucket (index).equals() checks logical equality between objects in that bucket.
If the contract is broken, collections like HashMap and HashSet can behave incorrectly — e.g., duplicate entries, lookup failures, or performance issues.
🔁 equals() Method Contract
The equals() method defines logical equality. The Java specification defines five rules it must follow:
1. Reflexive
An object must equal itself.
java
x.equals(x) == true
2. Symmetric
If one object equals another, the reverse must also be true.
java
if (x.equals(y)) then y.equals(x)
3. Transitive
If x.equals(y) and y.equals(z), then x.equals(z) must also be true.
java
if (x.equals(y) && y.equals(z)) then x.equals(z)
4. Consistent
If no changes are made, repeated invocations of equals() must return the same result.
java
x.equals(y) consistently returns true or false
5. Non-null
Comparing with null should always return false.
java
x.equals(null) == false
🔢 hashCode() Method Contract
The hashCode() method returns an integer used to determine an object’s hash bucket in a hash-based collection.
The rules:
1. Consistent
The hash code must stay the same unless the object’s fields used in equals() change.
java
x.hashCode() must return the same value on every call
2. Equal Objects → Equal Hash Codes
If two objects are equal via equals(), their hash codes must be the same.
java
if (x.equals(y)) then x.hashCode() == y.hashCode()
3. Unequal Objects → Prefer Different Hash Codes
Not mandatory, but it’s good practice for performance.
java
if (x.equals(y) == false) it's better if x.hashCode() != y.hashCode()
🚨 What Happens If You Break the Contract?
- Equal objects with different hash codes will go into different buckets → lookup fails.
- Unequal objects with the same hash code will go into the same bucket → performance drops due to linear scans.
💡 Correct Implementation Example
Here’s how you can safely override equals() and hashCode() using Java best practices:
java
import java.util.Objects;
public class Person {
private String name;
private int age;
// Constructor, getters, setters omitted for brevity
@Override
public boolean equals(Object o) {
if (this == o) return true; // Reflexive
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age); // Uses fields from equals()
}
}
Why this works:
equals() compares the same fields used in hashCode().Objects.hash() generates a reliable hash code.- Covers all required contract rules.
🛡️ Best Practices
- Always override both
equals() and hashCode() together. - Use
Objects.equals() and Objects.hash() for simplicity and null-safety. - Keep both methods consistent: the same fields must be used in both.
🎯 Summary
MethodPurposeKey Ruleequals()Logical equalityEqual objects must behave identicallyhashCode()Hash bucket indexEqual objects must have equal hash codes
Violating the contract breaks the behavior of collections like HashMap and HashSet.
By following this contract, you ensure that your Java classes behave reliably in collections and are safe for use in enterprise-grade applications.