Skip to main content

2.8 The equals() & hashCode() Contract

← Back to Chapter 2: Java Overview & Memory Model

Overriding equals() and hashCode() correctly is one of the most critical requirements for data structures like HashMap, HashSet, and Hashtable.


1. The Mathematical Contract​

Per the Java Language Specification (JLS):

  1. If two objects are equal via equals(Object), they MUST have the same hashCode():
    a.equals(b) == true ===> a.hashCode() == b.hashCode()
  2. If two objects have the same hashCode(), they are NOT necessarily equal (Hash Collision):
    a.hashCode() == b.hashCode() ===> a.equals(b) may be true OR false
  3. Consistency: Successive calls to hashCode() on the same object must return identical integers, provided state used in equals() is unmodified.

2. What Breaks If You Override equals() Without hashCode()?​

public class Product {
private final String sku;
public Product(String sku) { this.sku = sku; }

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Product p)) return false;
return Objects.equals(sku, p.sku);
}
// OMITTED hashCode()!
}

The Catastrophic Bug:​

Map<Product, Integer> inventory = new HashMap<>();
inventory.put(new Product("SKU-99"), 50);

// Retrieval fails!
Integer count = inventory.get(new Product("SKU-99"));
System.out.println(count); // PRINTS null!

Why? The new lookup key new Product("SKU-99") inherits default Object.hashCode() (derived from its memory address). It hashes to a different bucket in the HashMap, so the map never checks equals() and returns null.


3. Idiomatic Implementation with java.util.Objects​

import java.util.Objects;

public class Employee {
private final long id;
private final String email;

public Employee(long id, String email) {
this.id = id;
this.email = email;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return id == employee.id && Objects.equals(email, employee.email);
}

@Override
public int hashCode() {
return Objects.hash(id, email);
}
}