java hashmap 似乎允许 2 个重复键

java hashmap seems to allow 2 duplicate keys

执行以下代码:

public class Main {

    public static void main(String[] args) {
        Util util = new Util();
        util.addBlock(1);
        util.addBlocks(util.getBlocks());
    }

}

public class Util {

    public static HashMap<Long, Integer> blockMap = new HashMap<>();
    private Gson gson = new Gson();

    public void addBlocks(String json){
        Map map = gson.fromJson(json, Map.class);
        blockMap.putAll(map);
        System.out.println(blockMap.keySet());
    }

    public void addBlock(int i){
        blockMap.put(0L, i);
    }

    public String getBlocks(){
        return gson.toJson(blockMap);
    }
}

我得到了输出

[0, 0]

来自打印 System.out.println(blockMap.keySet());.

因此,出于某种原因,我有一个 Map<Long, Integer>,其中包含两个 Long,其中值 0 作为键。还有一个键集 Set<Long> 和两个 0。但是map和set不允许重复键,这怎么可能?


代码首先向地图添加一个简单的条目:

blockMap.put(0L, i);

然后,我通过使用 GSON 将映射转换为 JSON 字符串,然后再转换回映射来添加另一个条目:

gson.toJson(blockMap);
...
Map map = gson.fromJson(json, Map.class);
blockMap.putAll(map);

我希望它会覆盖之前的条目,而不是添加另一个具有相同、重复的键的条目。

说明

您对结果的解释略有错误。你说它打印

[0, 0]

并得出结论,您有两个类型为 Long 且值为 0 的键。但这实际上是错误的。一个确实是Long,来自这里:

blockMap.put(0L, i);

但另一个是 String "0",在印刷品中看起来一样。


原始类型

您可能会对为什么 HashMap<Long, Block> 中有一个字符串键感到困惑。毕竟,它将密钥指定为 Long,而不是 String.

问题在这里:

Map map = gson.fromJson(json, Map.class);
blockMap.putAll(map);

此处发生的情况是您使用 putAll,但提供 Map。请注意,它没有泛型,它是 原始类型

通过使用原始类型,您基本上牺牲了所有类型安全并承诺 Java "Yeah, yeah, trust me, I know what I am doing."。所以 Java 只会相信你实际上给了它一个 Map<Long, Block>,但你实际上给了它一个 Map<String, String>


堆污染

在 Java 中,泛型在运行时被删除。也就是说,通过编译后,Java 对泛型一无所知,基本上可以随意混合类型,或多或少只是“Map<Object, Object>”。

因此,您设法欺骗了 Java 的安全泛型类型系统,并将 String 注入到只接受 Long 作为键的映射中。这称为堆污染。这就是你永远不应该使用原始类型的主要原因。使用原始类型没有任何好处。

堆污染的一个小例子:

List<Dog> dogs = new ArrayList<>(); // safe collection
dogs.add(new Dog());

// dogs.add(new Cat()); // not allowed, dogs is safe

List raw = dogs; // raw handle to safe collection
raw.add(new Cat()); // works! heap pollution

// compiles, but throws at run-time,
// since it is actually a cat, not a dog
Dog dog = dogs.get(1);

备注

有关此主题的更多信息,请阅读:

正如@Zabuza 指出的那样,这里的问题是您使用的原始地图没有在运行时删除的泛型。试试这个例子:

for (Object key: Util.blockMap.keySet()) {
    System.out.println("key: " + key + " type: " + key.getClass());
}

您会看到第一个键的类型为 Long,而第二个键的类型为 String

因为 equals 方法在 String 中定义为:

if (anObject instanceof String) {
    // ...
}
return false;

等于总是return假

希望下面的代码能解开你的疑惑:

public void addBlocks(String json) {
    Map map = gson.fromJson(json, Map.class);
    blockMap.putAll(map);
    System.out.println(blockMap.keySet());
    Iterator itr=blockMap.keySet().iterator();
    while(itr.hasNext()){
        System.out.println(itr.next().getClass());
    }
}

使用此代码输出:

[0, 0]
class java.lang.Long
class java.lang.String