Java 使用Map作为键的WeakHashMap在修改键后返回空值的意外行为

Java 使用Map作为键的WeakHashMap在修改键后返回空值的意外行为,java,hashmap,hashcode,Java,Hashmap,Hashcode,我们需要缓存一些对象的信息,因此我们使用的是java.util.WeakHashMap。如果我们使用的键是java.util.HashMap,我们会看到意外的行为 例如: WeakHashMap<Object, Object> whm = new WeakHashMap<>(); Map<String, String> map = new HashMap<>(); whm.put(map, "map"); System.out.println(ma

我们需要缓存一些对象的信息,因此我们使用的是
java.util.WeakHashMap
。如果我们使用的键是
java.util.HashMap
,我们会看到意外的行为

例如:

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()));
调用
whm.put(map,“map”)
后,为什么
whm.get(map)
null

对于
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));

这与它是一个弱映射无关,与您修改映射键有关,这基本上是您应该避免做的事情。通过向映射添加条目,您正在更改其哈希代码。这很容易证明:

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());        
    }
}
import java.util.HashMap;
导入java.util.Map;
公开课考试{
公共静态void main(字符串[]args){
Map Map=newhashmap();
System.out.println(map.hashCode());
地图放置(“键”、“值”);
System.out.println(map.hashCode());
}
}
此时,尝试获取条目将失败,因为其哈希代码不再与插入的哈希代码匹配


AtomicInteger
既不重写
equals
也不重写
hashCode
,因此您得到的是对象标识相等-当您调用
set
时,其哈希代码不会更改这与它是弱映射无关,而与您修改映射键有关,这基本上是你应该避免做的事情。通过向映射添加条目,您正在更改其哈希代码。这很容易证明:

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());        
    }
}
import java.util.HashMap;
导入java.util.Map;
公开课考试{
公共静态void main(字符串[]args){
Map Map=newhashmap();
System.out.println(map.hashCode());
地图放置(“键”、“值”);
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;
    }
}
import java.util.IdentityHashMap;
导入java.util.Map;
导入java.util.WeakHashMap;
/**
*{@link WeakHashMap}还使用相等来支持可变键,更改{@link Object#hashCode()}如{@link IdentityHashMap}。在平等的情况下
*检查性能将是{@code O(n)}。目前只支持{@link-Map#get(Object)}和{@link-Map#containsKey(Object)}实现相等。
*
*@作者安德烈·舒尔茨
*/
公共类WeakIdentityMap扩展了WeakHashMap{
@凌驾
公共布尔containsKey(对象键){
布尔结果=super.containsKey(键);
如果(结果){
返回结果;
}
对于(Map.Entry:super.entrySet()){
if(entry.getKey()==key){
返回true;
}
}
返回false;
}
@凌驾
public V get(对象键){
V值=super.get(键);
if(值!=null){
返回值;
}
对于(Map.Entry:super.entrySet()){
if(entry.getKey()==key){
返回条目.getValue();
}
}
返回null;
}
}

我的解决方案是使用equality添加WeakMap,在本例中,equality wich可以根据需要工作:

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;
    }
}
import java.util.IdentityHashMap;
导入java.util.Map;
导入java.util.WeakHashMap;
/**
*{@link WeakHashMap}还使用相等来支持可变键,更改{@link Object#hashCode()}如{@link IdentityHashMap}。在平等的情况下
*检查性能将是{@code O(n)}。目前只支持{@link-Map#get(Object)}和{@link-Map#containsKey(Object)}实现相等。
*
*@作者安德烈·舒尔茨
*/
公共类WeakIdentityMap扩展了WeakHashMap{
@凌驾
公共布尔containsKey(对象键){
布尔结果=super.containsKey(键);
如果(结果){
返回结果;
}
对于(Map.Entry:super.entrySet()){
if(entry.getKey()==key){
返回true;
}
}
返回false;
}
@凌驾
public V get(对象键){
V值=super.get(键);
if(值!=null){
返回值;
}
对于(Map.Entry:super.entrySet()){
if(entry.getKey()==key){
返回条目.getValue();
}
}
返回null;
}
}

@André:或者你不需要使用可变键。(在另一个地图中使用一个地图作为键是相当奇怪的。)@André:或者你不需要使用可变键。(在另一个映射中使用一个映射作为键是相当奇怪的。)这不再是真正的HashMap,可能不应该扩展
WeakHashMap
。它是一种效率相当低的“WeakIdentityMap”,因为您没有使用hashcodes,关于故意破坏映射契约的注意事项也适用于此。这不再是真正的HashMap,可能不应该扩展
WeakHashMap
。它是一种效率相当低的“WeakIdentityMap”,因为您没有使用hashcodes,关于故意违反Map合同的注意事项也适用于此处。