In Java, the
HashMap implementation uses the hashCode() method to determine the storage bucket for a key and the equals() method to check for key equality. The default implementation in the Object class generates a hash code based on the object's memory address. This behavior creates a problem when using custom objects as keys: two distinct instances with identical data will produce different hash codes and be considered unequal.To illustrate this, consider a scenario where a
Product class is used as a key without overriding these methods.public class Product {
private int id;
public Product(int id) {
this.id = id;
}
@Override
public String toString() {
return "Product #" + id;
}
}The following test class demonstrates the lookup failure.
import java.util.HashMap;
import java.util.Map;
public class InventoryTest {
public static void main(String[] args) {
Map<Product, String> inventory = new HashMap<>();
// Store a product with ID 101
inventory.put(new Product(101), "High-end Laptop");
// Attempt to retrieve using a new instance with the same ID
Product searchKey = new Product(101);
System.out.println("Searching for " + searchKey);
System.out.println("Result: " + inventory.get(searchKey));
}
}The output shows that the value is not found:
Searching for Product #101
Result: nullAlthough a product with ID 101 was inserted, the lookup failed because the
searchKey instance has a different memory address. The HashMap calculates its hash differently from the original key, and the default equals method returns false.To enable key lookups based on object content, the class must override both
hashCode() and equals(). The hash code should be derived from the fields that define uniqueness. Here is a corrected version, ValidatedProduct, that implements these methods correctly.import java.util.Objects;
public class ValidatedProduct {
private int id;
public ValidatedProduct(int id) {
this.id = id;
}
@Override
public int hashCode() {
// Generate hash based on the ID field
return Objects.hash(id);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
ValidatedProduct other = (ValidatedProduct) obj;
return id == other.id;
}
@Override
public String toString() {
return "ValidatedProduct #" + id;
}
}Running the same logic with
ValidatedProduct yields the expected result.import java.util.HashMap;
import java.util.Map;
public class CorrectedInventoryTest {
public static void main(String[] args) {
Map<ValidatedProduct, String> inventory = new HashMap<>();
inventory.put(new ValidatedProduct(101), "High-end Laptop");
ValidatedProduct searchKey = new ValidatedProduct(101);
System.out.println("Searching for " + searchKey);
System.out.println("Result: " + inventory.get(searchKey));
}
}The execution now successfully locates the value:
Searching for ValidatedProduct #101
Result: High-end LaptopBy overriding
hashCode, both instances map to the same bucket. By overriding equals, the map confirms that the content of the keys matches, allowing the retrieval of the associated value.