equals() and hashCode(): The Contract and Why It Matters
Codevity Admin · 10/25/2026
Step 10 of 38 · Java Interview Preparation for Beginners · Stage 2: Object-oriented programming
Codevity Admin · 10/25/2026
"Why must you override hashCode() when you override equals()?" is one of the most asked Java questions, because the wrong answer causes real bugs in HashMap and HashSet.
== compares references for objects (are they the same object?), and values for primitives.equals() is a method of Object. The default implementation is the same as ==. Classes such as String, Integer and List override it to compare content.Without overriding equals(), two objects with identical data are not "equal".
For equals():
x.equals(x) is true.x.equals(y) is true exactly when y.equals(x) is true.x.equals(y) and y.equals(z), then x.equals(z).x.equals(null) is false.For hashCode():
A HashSet or HashMap first uses hashCode() to find the bucket, and only then calls equals() inside that bucket. If equal objects have different hash codes, the lookup goes to the wrong bucket and never even calls equals().
import java.util.*;
public class EqualsHashCode {
public static void main(String[] args) {
Set<BadUser> bad = new HashSet<>();
bad.add(new BadUser("asha"));
System.out.println(bad.contains(new BadUser("asha"))); // false (equals() is overridden but hashCode() is not)
Set<GoodUser> good = new HashSet<>();
good.add(new GoodUser("asha"));
System.out.println(good.contains(new GoodUser("asha"))); // true
System.out.println(good.add(new GoodUser("asha"))); // false (already present)
System.out.println(good.size()); // 1
}
}
class BadUser {
final String name;
BadUser(String name) { this.name = name; }
@Override public boolean equals(Object o) { return o instanceof BadUser other && name.equals(other.name); }
}
class GoodUser {
final String name;
GoodUser(String name) { this.name = name; }
@Override public boolean equals(Object o) { return o instanceof GoodUser other && name.equals(other.name); }
@Override public int hashCode() { return Objects.hash(name); }
}For BadUser, the default hashCode() is derived from the object's identity, so the two "equal" users almost always land in different buckets and the lookup fails. See the equals and hashCode snippet.
Objects.equals(a, b) and Objects.hash(a, b, c) handle null for you.equals(), check the type with instanceof (pattern matching since Java 16) or getClass(). Mention the trade-off: getClass() is stricter and avoids symmetry problems with subclasses.public class RecordEquality {
public static void main(String[] args) {
Point a = new Point(1, 2);
Point b = new Point(1, 2);
System.out.println(a.equals(b)); // true
System.out.println(a == b); // false
System.out.println(a); // Point[x=1, y=2]
System.out.println(a.hashCode() == b.hashCode()); // true
}
}
record Point(int x, int y) {}If a field used by hashCode() changes after the object is stored in a hash-based collection, the object is now in the wrong bucket and cannot be found. Use immutable keys: String, Integer, records, or classes with final fields.
import java.util.*;
public class MutableKey {
static class Key {
int id;
Key(int id) { this.id = id; }
@Override public boolean equals(Object o) { return o instanceof Key k && k.id == id; }
@Override public int hashCode() { return Integer.hashCode(id); }
}
public static void main(String[] args) {
Key key = new Key(1);
Set<Key> set = new HashSet<>();
set.add(key);
key.id = 2; // the key changes after it was stored
System.out.println(set.contains(key)); // false (it is now looked up in a different bucket)
System.out.println(set.size()); // 1 (and it is still stuck in the set)
}
}== vs equals()? Reference identity versus logical equality.hashCode()equals()equals()hashCode()GoodUserGoodUser