Contents

Java's contract (documented in Object) mandates:

Violating the contract causes silent bugs: objects that are "equal" may not be found in a HashMap or may appear as duplicates in a HashSet.

By default, Object.equals() performs reference equality — two references are equal only if they point to the same object in memory. Object.hashCode() returns a value derived from the object's memory address.

String a = new String("hello"); String b = new String("hello"); System.out.println(a == b); // false (different objects) System.out.println(a.equals(b)); // true (String overrides equals) System.out.println(a.hashCode() == b.hashCode()); // true (String overrides hashCode)

String, Integer, and other wrapper classes already override both methods correctly. For custom classes, you must override them yourself.

Follow this canonical pattern:

public class Employee { private int id; private String name; // Constructor, getters omitted for brevity @Override public boolean equals(Object obj) { if (this == obj) return true; // same reference if (obj == null) return false; // null check if (getClass() != obj.getClass()) return false; // type check Employee other = (Employee) obj; return this.id == other.id && Objects.equals(this.name, other.name); } }
Key steps:

A good hash code should distribute objects uniformly. The easiest approach is Objects.hash():

@Override public int hashCode() { return Objects.hash(id, name); // combine significant fields }

Manual implementation using the prime-multiplier pattern:

@Override public int hashCode() { int result = 17; // non-zero constant result = 31 * result + id; result = 31 * result + (name != null ? name.hashCode() : 0); return result; } Rule of thumb: Include the same fields in hashCode() that you use in equals(). Never include mutable fields that may change after the object is placed in a HashMap.

When you call map.put(key, value):

  1. HashMap calls key.hashCode() to determine the bucket index.
  2. Inside the bucket, it calls key.equals(existingKey) to find a match or insert a new entry.
Map<Employee, String> map = new HashMap<>(); Employee e1 = new Employee(1, "Alice"); Employee e2 = new Employee(1, "Alice"); // logically same as e1 map.put(e1, "Engineering"); // Without overriding equals/hashCode: e2 lookup FAILS (different hash) // With correct overrides: e2 lookup SUCCEEDS System.out.println(map.get(e2)); // "Engineering" — if overrides are correct

If hashCode() is not overridden, e1 and e2 land in different buckets and the get() call returns null.

Modern IDEs (IntelliJ, Eclipse) and libraries like Lombok (@EqualsAndHashCode) can auto-generate correct implementations. Java 16+ Records automatically generate correct equals/hashCode based on all component fields.