Implementing Custom Objects as HashMap Keys in Java

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: null
Although 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 Laptop
By 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.

Tags: java hashmap Object Equality hashCode equals

Posted on Sat, 09 May 2026 05:35:46 +0000 by elbowgrease