【问题标题】:java hashmap seems to allow 2 duplicate keysjava hashmap似乎允许2个重复的键
【发布时间】:2020-05-29 14:17:47
【问题描述】:

执行以下代码:

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&lt;Long, Integer&gt;,其中包含两个 Longs,其值为 0 作为键。还有一个键集Set&lt;Long&gt; 和两个0s。但是map和set不允许重复key,这怎么可能?


代码首先在地图中添加了一个简单的入口:

blockMap.put(0L, i);

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

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

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

【问题讨论】:

  • 你能发一个minimal reproducible example吗?
  • 我不同意。我刚刚尝试了提供的代码,这会打印出操作员所说的内容。
  • @Schottky OPs 代码不完整,您如何按原样执行它? Block 和 JSON 有效负载丢失。请分享完整代码,其他人也可以复制和调查。
  • @Zabuza 这就是问题所在。使用这个实现,一个键是 String,另一个键是 Long
  • 你基本上绕过了通用系统。所以有了这个,你基本上可以在你的地图中插入你想要的任何东西,而不仅仅是Long。该地图的工作原理仍然相同,但不允许重复。但是通过能够插入任何你想要的东西,你可以轻松地插入看起来相似但实际上不同的东西,比如字符串"0" vs 长0 vs 整数0 vs 短0,.. . 一切都会被认为是不同的。

标签: java json hashmap gson


【解决方案1】:

说明

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

[0, 0]

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

blockMap.put(0L, i);

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


原始类型

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

问题就在这里:

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

这里发生的是您使用putAll,但提供了Map。请注意,它没有泛型,它是一个原始类型

通过使用原始类型,您基本上牺牲了所有类型安全性并承诺 Java“是的,是的,相信我,我知道我在做什么。”。所以 Java 会相信你,你实际上给了它一个 Map&lt;Long, Block&gt;,但你实际上给了它一个 Map&lt;String, String&gt;


堆污染

在 Java 中,泛型在运行时被删除。这意味着,通过编译后,Java 对泛型一无所知,基本上允许您根据需要混合类型,它或多或少只是“Map&lt;Object, Object&gt;”。

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

堆污染的小例子:

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

注意事项

这里有更多关于这个主题的阅读:

【讨论】:

  • 为什么投反对票?我想改进帖子。
【解决方案2】:

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

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

你会看到你的第一个键的类型是Long,而第二个键的类型是String

由于 equals 方法在 String 内部定义为:

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

equals 总是返回 false

【讨论】:

    【解决方案3】:

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

    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
    

    【讨论】:

      猜你喜欢
      • 2013-12-30
      • 2016-06-09
      • 1970-01-01
      • 1970-01-01
      • 2019-02-06
      • 2023-03-16
      • 1970-01-01
      • 2021-10-15
      • 2018-08-31
      相关资源
      最近更新 更多