带有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不会覆盖equalshashCode,因此您可以获得对象标识相等性 - 当您拨打set时,其哈希码不会更改。

+0

正确,我的错...... - 因此,我们需要平等waek地图,而不是散列... –

+1

@ André:或者你不需要使用可变键。 (在另一张地图中使用地图作为关键字是相当奇怪的。) –

我的解决办法是添加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; 
    } 
} 
+0

这不再是一个真正的HashMap,应该不会扩展'WeakHashMap'。 – Hulk

+0

这是一种效率相当低的“WeakIdentityMap”,因为您没有使用哈希码,并注意到[IdentityHashMap的JavaDocs]中记录的警告(https://docs.oracle.com/javase/9​​/docs/api/java /util/IdentityHashMap.html)有关故意破坏地图合同也适用于此处。 – Hulk