Code Review · Java
Java Code Review: Missing equals() and hashCode()
Review Java code where HashSet or HashMap behaves incorrectly because a domain object does not implement equals() and hashCode().
Code Review Exercise
Review this code. The developer expects the duplicate email to be ignored because both users have the same email address.
import java.util.HashSet;
import java.util.Set;
class User {
private String email;
User(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
}
public class Demo {
public static void main(String[] args) {
Set<User> users = new HashSet<>();
users.add(new User("alice@example.com"));
users.add(new User("alice@example.com"));
System.out.println(users.size()); // expected 1?
}
}What issues do you see before reading the review?
Review Findings
Issue #1: User does not define logical equality
The User class does not override equals(). That means two separate User objects are only equal if they are the exact same object reference.
Issue #2: hashCode() is also missing
For hash-based collections such as HashSet andHashMap, overriding equals() is not enough. Equal objects must also return the same hashCode().
Issue #3: The collection behavior is surprising
The code looks like it should avoid duplicate users, butHashSet will store both objects because it sees them as different. That is a correctness bug, not just a style issue.
Issue #4: Equality needs a domain decision
Before writing equals(), decide what makes two users the same. Is it email, database id, username, or something else? The code review should call out that this is a business/design decision.
Why This Happens
In Java, the default equals() implementation inherited from Object behaves like reference equality. So these two objects are different unless the class defines what logical equality means.
new User("alice@example.com")
new User("alice@example.com")These contain the same email, but they are still two different objects in memory. A HashSet uses bothequals() and hashCode() to decide whether an item is already present.
Fixed Version
If the business rule says email uniquely identifies a user, implement both equals() and hashCode() using email.
import java.util.Objects;
class User {
private final String email;
User(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User user)) return false;
return Objects.equals(email, user.email);
}
@Override
public int hashCode() {
return Objects.hash(email);
}
}Even Better: Prefer Immutable Key Fields
If a field participates in equals() andhashCode(), avoid changing it after the object is placed into a hash-based collection.
That is why the fixed version uses:
private final String email;Mutable equality fields can cause a different but related bug: the object is stored in one hash bucket, then its hash changes, and the collection may no longer find it correctly.
What a Strong Review Might Say
This class is being used inside a HashSet, but it does not define equals() or hashCode(). As a result, duplicate users with the same email will not be treated as duplicates. We should decide what uniquely identifies a user and implement both methods consistently, preferably using immutable fields.
Interview Follow-Ups
Why do equals() and hashCode() need to be consistent?
Hash-based collections first use hashCode() to find a bucket, then use equals() to compare objects in that bucket. If equal objects have different hash codes, the collection may not find them correctly.
Is overriding equals() without hashCode() a problem?
Yes. If two objects are logically equal but return different hash codes, HashSet and HashMap behavior becomes incorrect.
Should every class override equals() and hashCode()?
No. Only do it when the class needs logical equality, especially when instances are used in sets, maps, or duplicate checks.
What is the safest kind of object to use as a map key?
An immutable object with stable equals() and hashCode() behavior. Strings, enums, records, and immutable value objects are common examples.