HashMap internals 13: Why mutable keys are dangerous
When you put a key, HashMap stores it in the bucket chosen by its hash code at that moment, and remembers that hash. If the fields used by hashCode() and equals() change afterwards:
get(key)calculates the new hash and looks in a different bucket, returningnulleven though the entry is still inside.- Even if it happened to look in the right bucket, the stored hash no longer matches.
containsKeyisfalse,removedoes nothing, butsize()and iteration still show the entry: a stranded entry and a memory leak.
The rules:
- Use immutable keys:
String,Integer, enums, records with immutable fields. - Never change fields used in
equals()/hashCode()while an object is a key (or aHashSetelement). - If you must change it: remove, change, then put back.
Example
class User {
private String email;
User(String email) { this.email = email; }
void setEmail(String email) { this.email = email; }
@Override public boolean equals(Object o) { return o instanceof User u && u.email.equals(email); }
@Override public int hashCode() { return email.hashCode(); }
}
Map<User, String> roles = new HashMap<>();
User asha = new User("asha@old.com");
roles.put(asha, "ADMIN"); // stored in bucket 4 (of 16)
asha.setEmail("asha@new.com"); // the key changes while it's inside the map
System.out.println(roles.get(asha)); // null get() now looks in bucket 11
System.out.println(roles.containsKey(asha)); // false
System.out.println(roles.size()); // 1 the entry is still there, stranded
roles.remove(asha); // removes nothingCommon mistake
Using an object as a key and later updating a field that equals() and hashCode() depend on.
Under the hood
The same trap hits HashSet (its elements are the keys of an internal HashMap) and TreeMap/TreeSet if fields used by compareTo() change. Records are only shallowly immutable: a record holding an ArrayList is still a dangerous key if the list changes. JPA entities are a classic case: an equals()/hashCode() based on a generated id changes when the entity is first saved, which is why Hibernate's documentation recommends a stable business key.
Check yourself
A key's email (used in hashCode) changes after put(). What does get(sameObject) return?
How this connects
Know these first
Where this leads
You've reached the end of this thread. Try a learning path for what's next.
Part of HashMap internals, part by part.
Was this lesson helpful?
Finished reading? Mark it complete to track your progress.