Q: What is the contract between ==, .equals(), and hashCode()?
Answer:
== (Reference Equality)
Compares memory addresses. Returns true only if both variables point to the exact same object on the heap.
String a = new String("hello");
String b = new String("hello");
System.out.println(a == b); // false — different objects
.equals() (Content Equality)
Compares the logical content of two objects. The default implementation in Object uses ==, so you must override it in your classes.
System.out.println(a.equals(b)); // true — String overrides equals()
hashCode()
Returns an integer hash used by hash-based collections (HashMap, HashSet). Must be consistent with equals().
The Contract (Critical!)
- If
a.equals(b)istrue, thena.hashCode() == b.hashCode()MUST betrue. - If
a.hashCode() != b.hashCode(), thena.equals(b)MUST befalse. - If
a.hashCode() == b.hashCode(),a.equals(b)may or may not betrue(hash collisions are allowed).
What Happens If You Break the Contract?
// ❌ BROKEN: overrides equals() but NOT hashCode()
public class Employee {
private int id;
private String name;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Employee)) return false;
Employee e = (Employee) o;
return id == e.id && Objects.equals(name, e.name);
}
// hashCode NOT overridden — uses default Object.hashCode() (memory address)
}
Employee e1 = new Employee(1, "Alice");
Employee e2 = new Employee(1, "Alice");
e1.equals(e2); // true ✅
Set<Employee> set = new HashSet<>();
set.add(e1);
set.contains(e2); // false! 💥 — different hashCode → looks in wrong bucket
Correct Implementation
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Employee e)) return false;
return id == e.id && Objects.equals(name, e.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
[!CAUTION] Always override
hashCode()when you overrideequals(). This is the #1 source of subtle bugs withHashMapandHashSet— objects that are logically equal but have different hash codes end up in different buckets and are treated as different entries.