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
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
.
See more on this question at Stackoverflow