Fundamentals 17 min read

Why Changing a HashMap Key’s Field Breaks Retrieval – The Hidden Pitfalls

This article explores how mutating the fields of an object used as a HashMap key corrupts its hash code, causing look‑ups to fail, and walks through the underlying source code of put, hash, getNode and related methods to reveal why this happens.

Alibaba Cloud Developer
Alibaba Cloud Developer
Alibaba Cloud Developer
Why Changing a HashMap Key’s Field Breaks Retrieval – The Hidden Pitfalls

1. Introduction

The article uses a concrete scenario where a programmer named Xiao Ming encounters a puzzling situation: after inserting a Player object as a key in a HashMap, changing the object's name field makes the entry impossible to retrieve.

2. Scenario Reproduction

A Player class is defined with a mutable name field, and equals and hashCode are overridden to depend on name. The map is populated with three players, then the name of one player ( kai) is changed from "Kai" to "Eric". Subsequent assertions show that myMap.containsKey(kai) and myMap.containsKey(eric) both return false, and myMap.get(...) returns null.

public class Player {
    private String name;
    public Player(String name) { this.name = name; }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Player)) return false;
        Player p = (Player) o;
        return name.equals(p.name);
    }
    @Override
    public int hashCode() { return name.hashCode(); }
}
Map<Player, Integer> myMap = new HashMap<>();
Player kai = new Player("Kai");
Player tom = new Player("Tom");
Player amanda = new Player("Amanda");
myMap.put(kai, 42);
myMap.put(tom, 200);
myMap.put(amanda, 88);
assertTrue(myMap.containsKey(kai));

// Change Kai's name to Eric
kai.setName("Eric");
assertEquals("Eric", kai.getName());
Player eric = new Player("Eric");
assertEquals(eric, kai);
assertFalse(myMap.containsKey(kai));
assertFalse(myMap.containsKey(eric));
assertNull(myMap.get(kai));
assertNull(myMap.get(eric));

3. Source Code Analysis

3.1 put method

The HashMap#put method delegates to putVal after computing the hash of the key.

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

3.2 hash method

The hash function spreads the high bits of key.hashCode() by XOR‑ing with the value shifted right 16 bits, reducing collisions.

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

3.3 putVal method

putVal

locates the bucket using the computed hash, handles empty buckets, linked‑list chains, and treeified bins, inserts a new node or updates an existing one, and resizes the table when necessary.

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    // initialize table if needed
    // locate bucket
    // handle existing key or create new node
    // resize if size exceeds threshold
    // return previous value or null
}

3.4 containsKey method

containsKey

simply checks whether getNode(hash(key), key) returns a non‑null node.

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

3.5 getNode method

getNode

locates the bucket, then traverses either a linked list or a red‑black tree to find a node whose hash and key match.

final Node<K,V> getNode(int hash, Object key) {
    // locate bucket
    // check first node
    // traverse list or tree
    // return matching node or null
}

4. Insights

The key takeaway is that a key’s hash code must remain stable while it resides in a HashMap. Mutating fields that participate in hashCode (or equals) changes the bucket location, making the entry invisible to look‑ups and potentially causing memory leaks or performance degradation.

4.1 Never modify a HashMap key

Design key objects to be immutable, e.g., declare fields final and omit setters.

public final class Player {
    private final String name;
    public Player(String name) { this.name = name; }
    public String getName() { return name; }
    @Override public boolean equals(Object o) { /* same as before */ }
    @Override public int hashCode() { return name.hashCode(); }
}

4.2 Defensive programming

When creating custom key classes, carefully decide whether to override equals and hashCode. Incorrect implementations can lead to duplicate keys, unexpected overwrites, or lost entries.

HashMap key mutation illustration
HashMap key mutation illustration
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaHashMapCollectionsBestPracticesMutableKey
Alibaba Cloud Developer
Written by

Alibaba Cloud Developer

Alibaba's official tech channel, featuring all of its technology innovations.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.