Skip to content
intermediatePhase 16 · Java Memory & JVM

equals() and hashCode()

Override equals and hashCode correctly for HashMap and HashSet usage.

1h
3 problems
Topic Progress0%

Default equals()

Default equals() Behavior

By default, Object.equals() performs reference comparison (identity check).

public class DefaultEquals {
    public static void main(String[] args) {
        String s1 = new String("hello");
        String s2 = new String("hello");
        
        // Default equals: compares references
        System.out.println(s1 == s2);          // false (different objects)
        System.out.println(s1.equals(s2));     // true (String overrides equals)
        
        // Custom class without override
        Person p1 = new Person("Alice", 30);
        Person p2 = new Person("Alice", 30);
        System.out.println(p1.equals(p2));     // false! (reference comparison)
    }
}

class Person {
    String name;
    int age;
    
    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // Uses Object.equals() - reference comparison
}

Why it matters:

  • == checks reference equality
  • equals() can be overridden for logical equality
  • Most classes override equals() (String, Integer, collections)

Overriding equals()

Overriding equals() Correctly

The equals() contract requires five properties:

  1. Reflexive: x.equals(x) must be true
  2. Symmetric: x.equals(y) iff y.equals(x)
  3. Transitive: x.equals(y) and y.equals(z) implies x.equals(z)
  4. Consistent: Same result across multiple calls
  5. Non-null: x.equals(null) must be false
public class Person {
    private String name;
    private int age;
    private String email;
    
    // Correct equals() implementation
    @Override
    public boolean equals(Object obj) {
        // 1. Check reference equality
        if (this == obj) return true;
        
        // 2. Check null and class
        if (obj == null || getClass() != obj.getClass()) return false;
        
        // 3. Cast
        Person other = (Person) obj;
        
        // 4. Compare fields
        return age == other.age
            && Objects.equals(name, other.name)
            && Objects.equals(email, other.email);
    }
    
    // MUST override hashCode() too!
    @Override
    public int hashCode() {
        return Objects.hash(name, age, email);
    }
}

Using IDE-generated equals(): Most IDEs (IntelliJ, Eclipse) can generate equals() and hashCode() methods for you.

hashCode() Contract

hashCode() Contract

The contract has three rules:

  1. Consistent: Same object must return same hash code across calls (unless fields used in hashCode change)
  2. Equal objects must have equal hash codes: If x.equals(y) is true, then x.hashCode() == y.hashCode()
  3. Unequal objects may have equal hash codes (hash collisions)
public class HashCodeExample {
    public static void main(String[] args) {
        Person p1 = new Person("Alice", 30);
        Person p2 = new Person("Alice", 30);
        
        // Contract: equal objects → same hash code
        System.out.println(p1.equals(p2));         // true
        System.out.println(p1.hashCode() == p2.hashCode());  // MUST be true
        
        // Unequal objects can have same hash code
        Person p3 = new Person("Bob", 25);
        // p3.hashCode() might equal p1.hashCode() (collision)
        // but p1.equals(p3) is false
    }
}

// BAD hashCode() - always returns 1
class BadHashCode {
    @Override
    public int hashCode() { return 1; }  // Every object has same hash!
}

Why it matters: HashMap and HashSet use hash codes to find buckets. Bad hash codes degrade O(1) to O(n).

HashMap Impact

HashMap Behavior with Bad equals/hashCode

public class HashMapDemo {
    public static void main(String[] args) {
        // Bad: only overrides equals, not hashCode
        Map<Person, String> map = new HashMap<>();
        
        Person p1 = new Person("Alice", 30);
        map.put(p1, "Engineer");
        
        // This should work but WON'T with bad hashCode!
        Person p2 = new Person("Alice", 30);  // equal to p1
        System.out.println(map.get(p2));  // null! (different bucket)
        System.out.println(map.containsKey(p2));  // false!
    }
}

// Why it fails:
// 1. p1.hashCode() = some value → bucket A
// 2. p2.hashCode() = DIFFERENT value → bucket B
// 3. HashMap looks in bucket B, doesn't find p1
// 4. Returns null

HashMap's algorithm:

  1. Compute key.hashCode()
  2. Find bucket: hash & (n-1)
  3. Search bucket for key using equals()

If hashCode() is wrong:

  • Equal objects go to different buckets
  • get(), containsKey(), remove() fail silently
  • No error, just wrong results

Common Mistakes

Common equals/hashCode Mistakes

Mistake 1: Using mutable fields in hashCode

// BAD - field changes affect hashCode
public class MutablePerson {
    String name;
    int age;
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age);  // age changes → different hash!
    }
}

Mistake 2: Forgetting null checks

// BAD - NPE if name is null
@Override
public boolean equals(Object obj) {
    Person other = (Person) obj;
    return this.name.equals(other.name);  // NPE!
}

// GOOD - use Objects.equals()
@Override
public boolean equals(Object obj) {
    Person other = (Person) obj;
    return Objects.equals(this.name, other.name);
}

Mistake 3: Wrong field types in equals

// BAD - compares all fields including ID
public boolean equals(Object obj) {
    return this.id == other.id
        && this.name.equals(other.name);  // Two people with same name are NOT equal
}

// GOOD - compare only meaningful fields
public boolean equals(Object obj) {
    return Objects.equals(this.name, other.name)
        && this.age == other.age;
}

Mistake 4: Not overriding both together

// ALWAYS override both equals() AND hashCode()
// Java contract requires it!

Rules to remember:

  1. Always override both equals() and hashCode()
  2. Use immutable fields in hashCode()
  3. Use Objects.equals() for null-safe comparison
  4. Include only fields that define equality

Practice Problems

0/3solved
Fix Broken equals/hashCode

This class has a bug. When used in a HashMap, `containsKey()` returns false even for equal objects. Find and fix the issue: ```java class Employee { String name; int id; public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof Employee)) return false; Employee other = (Employee) obj; return id == other.id && name.equals(other.name); } // No hashCode() } ```

Solution
The equals() method is correct, but **hashCode() is not overridden**. The default `Object.hashCode()` returns a memory address-based hash, so two equal Employee objects have different hash codes. Fix:

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

Without hashCode(), `map.get(new Employee("Alice", 1))` returns null because the equal object is in a different bucket.
Symmetry Violation

Does this equals() implementation satisfy the symmetry contract? If not, why? ```java class Number { int value; public boolean equals(Object obj) { if (obj instanceof Number) { return value == ((Number) obj).value; } if (obj instanceof Integer) { return value == (Integer) obj; } return false; } } ```

Solution
No, it violates **symmetry**.

- `Number(5).equals(Integer(5))` → true (Number checks instanceof Integer)
- `Integer(5).equals(Number(5))` → false (Integer doesn't know about Number)

This breaks the symmetry contract. Fix: only compare objects of the same class, or use a common interface.
Hash Collision Impact

A HashSet has 1000 elements, all with hashCode() = 1. What happens when you call contains()? What is the time complexity?

Solution
With all elements in the same bucket, `contains()` must call `equals()` on **all 1000 elements**. Time complexity: **O(n)** instead of O(1).

This is why a good hashCode() is critical. Each element should spread evenly across buckets.

Quiz

1. What is the default behavior of Object.equals()?

Question 1 options

2. Which is a requirement of the hashCode() contract?

Question 2 options

3. Why must equals() and hashCode() be overridden together?

Question 3 options

4. What happens if HashMap contains a key with bad hashCode()?

Question 4 options

5. Which equals() violation means: if A.equals(B) then B.equals(A)?

Question 5 options

Flashcards

Question

What are the 5 properties of the equals() contract?

Answer

Reflexive (x=x), Symmetric (x=y → y=x), Transitive (x=y, y=z → x=z), Consistent (same result), Non-null (x≠null).

Question

What happens if you override equals() but not hashCode()?

Answer

Equal objects may have different hash codes, causing HashMap/HashSet to treat them as different keys. get(), containsKey(), and remove() will fail.

Question

How do you implement hashCode() correctly?

Answer

Use Objects.hash(field1, field2, ...) with the same fields used in equals(). Use only immutable fields.

Question

What is a hash collision?

Answer

When two unequal objects have the same hashCode(). This is allowed by the contract and handled by chaining in HashMap.

Question

What is the time complexity of HashMap.get() with perfect hash codes?

Answer

O(1) - constant time. With bad hash codes (all same), it degrades to O(n).

Revision Notes

Key Takeaways

  • 1.Default equals() is reference comparison (==)
  • 2.hashCode() must be consistent with equals()
  • 3.HashMap relies on both equals() and hashCode()
  • 4.Always override both methods together

Interview Tips

  • Recall all 5 equals() properties (reflexive, symmetric, transitive, consistent, non-null)
  • Explain how HashMap uses hashCode() then equals()
  • Mention Objects.hash() and Objects.equals() as clean implementations
  • Discuss why mutable fields in hashCode() cause bugs

Cheat Sheet

equals/hashCode Cheat Sheet

equals() Contract

  1. Reflexive: x.equals(x) = true
  2. Symmetric: x.equals(y) → y.equals(x)
  3. Transitive: x=y, y=z → x=z
  4. Consistent: same result every time
  5. Non-null: x.equals(null) = false

hashCode() Contract

  1. Consistent within same execution
  2. Equal objects → same hashCode
  3. Unequal objects → CAN have same hashCode

Rules

  • ALWAYS override both
  • Use Objects.hash() for hashCode
  • Use Objects.equals() for null-safe
  • Use immutable fields only