In Java, the contract between equals() and hashCode() is fundamental for any developer. If you override one, you must override the other. Breaking this rule leads to bugs where objects are "lost" inside collections like HashMap or HashSet.
In this guide, we will look at a correct implementation using a Book class, examine a broken implementation in an Employee class, and review key class design pitfalls.
Imagine a library sorting room where books are sorted into physical boxes labeled by their ISBN:
- The Shelf Box (
hashCode()): When a book arrives, you look up its ISBN to know which shelf box to put it in. This is the **hashCode**. - Checking the Book (
equals()): If you want to check if the book is already in that box, you do a detailed page comparison. This is the **equals** check.
If two books have the exact same ISBN (i.e. they are equal), they MUST go into the same box (return the same hash code). If they go into different boxes because their hash codes don't match, you will never find them!
1. The Correct Implementation: Book.java
Let's look at the Book class, which demonstrates a correct, clean contract. A book is uniquely identified by its ISBN:
public class Book {
private int ISBN;
private String author, title;
private int pageCount;
@Override
public int hashCode() {
return ISBN; // Matches the equals comparison field
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Book)) {
return false;
}
Book other = (Book) obj;
return this.ISBN == other.ISBN;
}
}
Since equals() compares ISBN and hashCode() returns ISBN, equal books will always produce identical hash codes. This is a perfect implementation.
2. Pitfalls in the Broken Implementation: Employee.java
Now let's examine the Employee class from our codebase. It contains three major bugs:
public class Employee {
private int empId;
private String fName, lName;
private int yearStarted;
public int hashcode() { // Pitfall 1: Lowercase 'c'
return this.yearStarted + empId;
}
@Override
public boolean equals(Object obj) {
return this.yearStarted == ((Employee) obj).yearStarted; // Pitfall 2 & 3
}
}
Notice the method is named hashcode() with a lowercase c. Because of this typo, Java does not override the standard Object.hashCode(). It treats it as a brand new method. HashMap will still call the default memory address-based hash code, causing equal employees to be lost in different buckets.
Solution: Always use the @Override annotation. The compiler will trigger an error if the method name is misspelled.
The equals() method immediately casts obj to Employee. If you call employee.equals("John"), the program will crash with a ClassCastException. Always verify with instanceof first.
The equals() method only checks yearStarted. This means any two employees who started working in the same year are considered identical! An employee should be compared using their unique ID (like empId).
Conclusion
When designing your objects:
- Always override
hashCode()whenever you overrideequals(). - Double-check capitalization: it's
hashCode()with a capitalC. - Guard your casts in `equals()` using `instanceof` checks.