In Java, HashMap is a highly efficient structure for looking up data. Under the hood, it uses two core methods inherited from the `Object` class: hashCode() and equals(). If you modify one without adhering to the correct contract, you can break the HashMap, causing duplicate entries, memory leaks, and lookup failures.
In this guide, we will explain this essential Java contract using a broken mailbox analogy and walk through its execution logic step by step.
Imagine a school sorting room with numbered **mailboxes**. When a mail sorter gets a letter, they use two steps to deliver and retrieve letters:
- Step 1: The Box Number (`hashCode()`): The sorter looks at the name and converts it to a number. For example, any name starting with "A" gets sorted into Mailbox 1. This is the **hashCode**.
- Step 2: Checking the ID (`equals()`): If Mailbox 1 already has letters, the sorter must check the name on the envelope to find the correct letter. This is the **equals** check.
Now imagine we break the rules. We write a contract where:
- Every envelope we send gets placed in Mailbox 1 (
hashCode()always returns 1). - The ID check always returns **false** (
equals()always says: "No, this is a different envelope").
When you send two letters addressed to "A", the sorter places the first letter in Mailbox 1. For the second letter, they go to Mailbox 1, check the name against the first letter, but because the equals check is broken, they think they are different and put the second letter in Mailbox 1 too. Mailbox 1 is now cluttered with duplicate letters, and you can never retrieve any letter because the sorter's identity check always says "Access Denied"!
Walkthrough of the Main Method Scenario
Let's trace how the program executes step-by-step from the entry point of the main method:
The program creates two distinct instances of the class TestTest, both having the internal name "A":
TestTest ob1 = new TestTest("A");
TestTest ob2 = new TestTest("A");
Even though they contain the same string value, they occupy different locations in memory.
Next, the program instantiates a HashMap and attempts to put both objects inside:
HashMap<TestTest, String> map = new HashMap<>();
map.put(ob1, ob1.getName());
map.put(ob2, ob2.getName());
- First Put: The map evaluates
ob1.hashCode(). The overridden method returns1, so the entry is stored in bucket 1. - Second Put: The map evaluates
ob2.hashCode(). It also returns1. The map goes to bucket 1 and finds `ob1` already exists. It then callsob2.equals(ob1). Because `equals()` is overridden to always returnfalse, the map concludes they are different keys and adds `ob2` to bucket 1 as a new node, creating a **duplicate key**!
Finally, we print the Map representation:
System.out.println(map);
It prints: {TestTest{name='A'}='A', TestTest{name='A'}='A'}. The map has a size of 2, proving we have violated the unique-key guarantee of the Map interface.
Java Implementation
Below is the complete Java code demonstrating a broken contract leading to duplicated keys in a HashMap:
package io.practise.accolite;
import java.util.HashMap;
public class Main {
public static void main(String[] args) {
TestTest ob1 = new TestTest("A");
TestTest ob2 = new TestTest("A");
HashMap<TestTest, String> map = new HashMap<>();
map.put(ob1, ob1.getName());
map.put(ob2, ob2.getName());
System.out.println(map);
}
}
class TestTest {
private String name;
TestTest(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object o) {
// Warning: Violates reflexive/symmetric rules by always returning false
return false;
}
@Override
public int hashCode() {
// Warning: All objects will collide inside bucket index 1
return 1;
}
@Override
public String toString() {
return "TestTest{" + "name='" + name + '\'' + '}';
}
}
Conclusion
The contract of hashCode() and equals() states that if two objects are equal according to the equals(Object) method, they must produce the same integer result from hashCode(). Furthermore, equals must be consistent and reflexive. Breaking this rule compromises the internal hashing buckets, causing maps and sets to behave unpredictably.