equals() and hashCode()

Object comparison and hashing

Interview Relevant: Very common interview topic
7 min read

equals() and hashCode() in Java

These methods define how objects are compared and hashed. Properly implementing them is essential for using objects in collections.

The Contract

  • equals(): Checks if two objects are "equal" in value
  • hashCode(): Returns an integer hash for the object
  • Rule: If a.equals(b), then a.hashCode() == b.hashCode()
  • Note: Same hashCode doesn't mean equals (collisions allowed)

⚠️ Critical: If you override equals(), you MUST override hashCode()! Breaking this contract breaks HashSet, HashMap, etc.

🔑 Interview Question: Why override both? Because hash-based collections use hashCode() to find bucket, then equals() to find exact match.

Code Examples

Problem with default equals/hashCode

java
1// The Problem: Default behavior
2public class Person {
3    private String name;
4    private int age;
5    
6    public Person(String name, int age) {
7        this.name = name;
8        this.age = age;
9    }
10}
11
12Person p1 = new Person("Alice", 25);
13Person p2 = new Person("Alice", 25);
14
15// Default equals() uses == (reference comparison)
16System.out.println(p1.equals(p2));  // false! Different objects
17System.out.println(p1 == p2);       // false! Different references
18
19// This breaks collections!
20Set<Person> set = new HashSet<>();
21set.add(p1);
22set.add(p2);
23System.out.println(set.size());  // 2! But we wanted 1 unique person
24
25Map<Person, String> map = new HashMap<>();
26map.put(p1, "Engineer");
27System.out.println(map.get(p2));  // null! Can't find with "equal" key

Proper equals() implementation

java
1// Proper equals() implementation
2public class Person {
3    private String name;
4    private int age;
5    
6    public Person(String name, int age) {
7        this.name = name;
8        this.age = age;
9    }
10    
11    @Override
12    public boolean equals(Object obj) {
13        // 1. Same reference? Return true
14        if (this == obj) return true;
15        
16        // 2. Null or different class? Return false
17        if (obj == null || getClass() != obj.getClass()) return false;
18        
19        // 3. Cast and compare fields
20        Person other = (Person) obj;
21        return age == other.age && 
22               Objects.equals(name, other.name);  // Null-safe!
23    }
24}
25
26// equals() contract:
27// 1. Reflexive: x.equals(x) == true
28// 2. Symmetric: x.equals(y) == y.equals(x)
29// 3. Transitive: x.equals(y) && y.equals(z) → x.equals(z)
30// 4. Consistent: Multiple calls return same result
31// 5. x.equals(null) == false

Proper hashCode() with equals()

java
1// Proper hashCode() implementation
2public class Person {
3    private String name;
4    private int age;
5    
6    public Person(String name, int age) {
7        this.name = name;
8        this.age = age;
9    }
10    
11    @Override
12    public boolean equals(Object obj) {
13        if (this == obj) return true;
14        if (obj == null || getClass() != obj.getClass()) return false;
15        Person other = (Person) obj;
16        return age == other.age && Objects.equals(name, other.name);
17    }
18    
19    @Override
20    public int hashCode() {
21        // Use Objects.hash() for convenience
22        return Objects.hash(name, age);
23        
24        // Or manual implementation:
25        // int result = 17;
26        // result = 31 * result + (name != null ? name.hashCode() : 0);
27        // result = 31 * result + age;
28        // return result;
29    }
30}
31
32// Now collections work correctly!
33Person p1 = new Person("Alice", 25);
34Person p2 = new Person("Alice", 25);
35
36Set<Person> set = new HashSet<>();
37set.add(p1);
38set.add(p2);
39System.out.println(set.size());  // 1! Correct!
40
41Map<Person, String> map = new HashMap<>();
42map.put(p1, "Engineer");
43System.out.println(map.get(p2));  // Engineer! Found it!

What happens when contract is broken

java
1// What happens if you break the contract?
2public class BadPerson {
3    private String name;
4    
5    // Overrides equals but NOT hashCode - WRONG!
6    @Override
7    public boolean equals(Object obj) {
8        if (obj instanceof BadPerson) {
9            return name.equals(((BadPerson) obj).name);
10        }
11        return false;
12    }
13    // Missing hashCode()!
14}
15
16BadPerson p1 = new BadPerson("Alice");
17BadPerson p2 = new BadPerson("Alice");
18
19System.out.println(p1.equals(p2));  // true
20
21Set<BadPerson> set = new HashSet<>();
22set.add(p1);
23set.add(p2);
24System.out.println(set.size());  // 2! WRONG! Should be 1
25
26// Why? HashMap/HashSet use hashCode() first!
27// Different hashCodes → different buckets → equals() never called
28
29// Modern Java: Use records (Java 16+) for automatic implementation
30public record PersonRecord(String name, int age) {
31    // equals(), hashCode(), toString() auto-generated!
32}

Use Cases

  • Using objects in HashSet
  • Using objects as HashMap keys
  • Removing duplicates from collections
  • Finding objects in collections
  • Value object comparison
  • Unit testing equality

Common Mistakes to Avoid

  • Overriding equals() without hashCode()
  • Using instanceof instead of getClass() (inheritance issues)
  • Not handling null fields
  • Mutable fields in hashCode() (objects "move" in HashSet)
  • Not using Objects.equals() for null-safety
  • Including non-significant fields