Unexpected behavior of WeakHashMap with Map as key returning null value after modification of key

We need to cache some information about some objects therefore we are using java.util.WeakHashMap. If we the key is java.util.HashMap we are seeing unexpected behavior.

Example:

WeakHashMap<Object, Object> whm = new WeakHashMap<>();
Map<String, String> map = new HashMap<>();
whm.put(map, "map");
System.out.println(map + ": " + whm.get(map) + " " + whm + " " + whm.containsKey(map));

map.put("key", "value");
System.out.println(map + ": " + whm.get(map) + " " + whm + " " + whm.containsKey(map));
System.out.println(map.hashCode());
System.out.println(whm.entrySet().stream().map(e -> e.getKey().hashCode()).collect(Collectors.toList()));
System.out.println(whm.entrySet().stream().map(e -> e.getKey() == map).collect(Collectors.toList()));

output is:

{}: map {{}=map} true
{key=value}: null {{key=value}=map} false
112004910
[112004910]
[true]

Why is whm.get(map) null after calling whm.put(map, "map")?

Same result for java.util.HashSet...

For AtomicInteger it works as expected:

WeakHashMap<Object, Object> whm = new WeakHashMap<>();
AtomicInteger integer = new AtomicInteger(0);
whm.put(integer, "integer");
System.out.println(integer + ": " + whm.get(integer) + " " + whm + " " + whm.containsKey(integer));

integer.set(1);
System.out.println(integer + ": " + whm.get(integer) + " " + whm + " " + whm.containsKey(integer));

results in:

0: integer {0=integer} true
1: integer {1=integer} true
Jon Skeet
people
quotationmark

This has nothing to do with it being a weak map, and everything to do with you modifying a map key, which is basically something you should avoid doing. By adding an entry to the map, you're changing its hash code. This is easily demonstrated:

import java.util.HashMap;
import java.util.Map;

public class Test {

    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        System.out.println(map.hashCode());
        map.put("key", "value");
        System.out.println(map.hashCode());        
    }
}

At that point trying to fetch the entry will fail, because its hash code no longer matches the hash code it was inserted with.

AtomicInteger doesn't override either equals or hashCode, so you get object identity equality instead - its hash code doesn't change when you call set.

people

See more on this question at Stackoverflow