带有Map的WeakHashMap的意外行为在修改密钥后返回空值的映射
问题描述:
我们需要缓存一些关于某些对象的信息,因此我们使用的是java.util.WeakHashMap
。如果我们的关键是java.util.HashMap
我们看到意想不到的行为。带有Map的WeakHashMap的意外行为在修改密钥后返回空值的映射
例子:
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()));
输出为:
{}: map {{}=map} true
{key=value}: null {{key=value}=map} false
112004910
[112004910]
[true]
为什么whm.get(map)
null
调用whm.put(map, "map")
后?
同样的结果为java.util.HashSet
...
对于AtomicInteger
它按预期工作:
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));
结果:
0: integer {0=integer} true
1: integer {1=integer} true
答
这有什么用它做是一个弱地图,以及与您修改地图键有关的所有事情,这基本上是您应该避免做的事情。通过添加一个条目到地图中,你正在改变它的哈希码。这很容易证明:
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());
}
}
在这一点试图获取条目将失败,因为它的哈希代码不再匹配它所插入的哈希代码。
AtomicInteger
不会覆盖equals
或hashCode
,因此您可以获得对象标识相等性 - 当您拨打set
时,其哈希码不会更改。
答
我的解决办法是添加WeakMap使用根据需要在这种情况下平等至极的工作原理:
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.WeakHashMap;
/**
* {@link WeakHashMap} also using equality to support mutable keys with changing {@link Object#hashCode()} like {@link IdentityHashMap}. In case of equality
* checking the performance will be {@code O(n)}. <b>Currently just {@link Map#get(Object)} and {@link Map#containsKey(Object)} are supported for equality.</b>
*
* @author Andre Schulz
*/
public class WeakIdentityMap<K, V> extends WeakHashMap<K, V> {
@Override
public boolean containsKey(Object key) {
boolean result = super.containsKey(key);
if (result) {
return result;
}
for (Map.Entry<K, V> entry : super.entrySet()) {
if (entry.getKey() == key) {
return true;
}
}
return false;
}
@Override
public V get(Object key) {
V value = super.get(key);
if (value != null) {
return value;
}
for (Map.Entry<K, V> entry : super.entrySet()) {
if (entry.getKey() == key) {
return entry.getValue();
}
}
return null;
}
}
正确,我的错...... - 因此,我们需要平等waek地图,而不是散列... –
@ André:或者你不需要使用可变键。 (在另一张地图中使用地图作为关键字是相当奇怪的。) –