Java - equals() and hashCode()

In Java, equals() and hashCode() are two closely related methods defined in Object. Overriding them correctly is essential whenever objects are used as keys in HashMap/HashSet or compared for logical equality.

Contents

The equals–hashCode Contract

Java's contract (documented in Object) mandates:

  • If a.equals(b) is true, then a.hashCode() == b.hashCode() must also be true.
  • If a.hashCode() == b.hashCode(), it does not mean a.equals(b) (hash collisions are allowed).
  • equals() must be reflexive, symmetric, transitive, consistent, and handle null gracefully.
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.

Default Behavior (Object class)

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.

Java
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.

Overriding equals()

Follow this canonical pattern:

Java
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:
  • Return true immediately if this == obj.
  • Return false for null or different class.
  • Cast and compare each significant field. Use Objects.equals() for nullable fields to avoid NPE.

Overriding hashCode()

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

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

Manual implementation using the prime-multiplier pattern:

Java
@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.

How HashMap Uses Both Methods

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.
Java
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.

Common Pitfalls

  • Overriding only equals: objects are logically equal but have different hash codes; they won't be found in collections.
  • Mutable key fields: mutating a field used in hashCode() after inserting into a HashMap changes its bucket; the entry becomes unreachable.
  • Using instanceof in equals: can break symmetry in class hierarchies; prefer getClass() unless designing for polymorphism.
  • Not calling super.hashCode() in inheritance: only do so if the parent's fields should also be part of equality.

Note: 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.