HashMap中键的突变会导致错误的结果

时间:2017-08-31 08:54:22

标签: java hashmap

在我的项目中我使用HashMap来存储一些数据,我最近发现当我改变HashMap的键时,可能会发生一些意想不到的错误结果。例如:

HashMap<ArrayList,Integer> a = new HashMap<>();
ArrayList list1 = new ArrayList<>();
a.put(list1, 1);
System.out.println(a.containsKey(new ArrayList<>())); // true
list1.add(5);
ArrayList list2 = new ArrayList<>();
list2.add(5);
System.out.println(a.containsKey(list2)); // false

请注意,a.keySet().iterator().next().hashCode() == list2.hashCode()a.keySet().iterator().next().equals(list2)都是真的。

我无法理解为什么会发生这种情况,指的是这两个对象相等且具有相同的哈希码。有谁知道是什么原因,如果有任何其他类似的结构,允许键的变异?感谢。

5 个答案:

答案 0 :(得分:4)

可变键始终是个问题。如果突变可能改变其哈希码和/或equals()的结果,则认为密钥是可变的。话虽这么说,列表经常生成他们的哈希码并根据他们的元素检查相等性,因此他们几乎从不是地图键的好选择。

你的例子中有什么问题?添加密钥时,它是一个空列表,因此产生的哈希码与包含元素时的哈希码不同。因此,即使在更改密钥列表之后密钥和list2的哈希码相同,您也找不到该元素。为什么?只是因为地图看起来错了。

示例(简化):

让我们从一些假设开始:

  • 空列表返回的哈希码为0
  • 如果列表包含元素5,则返回哈希码5
  • 我们的地图有16个桶(默认)
  • 存储桶索引由hashcode%16(我们的存储桶数量)确定

如果现在添加空列表,由于其哈希码,它会被插入到桶0中。

当您使用list1进行查找时,由于哈希码为5,它将在第5桶中查找。由于该桶为空,因此无法找到任何内容。

问题是你的密钥列表改变了它的哈希码,因此应该把它放到一个不同的桶中,但地图并不知道这应该发生(这样做可能会导致一堆其他问题)。

答案 1 :(得分:3)

根据Map的javadoc:

  

注意:如果将可变对象用作地图,则必须非常小心   键。如果对象的值,则不指定映射的行为   以一种影响等于比较的方式改变   对象是地图中的关键。这一禁令的一个特例是   地图不允许将自己包含为关键字。虽然它   允许地图将自己包含为一个值,极其谨慎   建议:equals和hashCode方法不再明确定义   在这样的地图上。

您的列表是关键,您正在更改它们。如果列表的内容不是确定哈希码的值和什么是相等的,那么这不是问题,但这不是你的情况。如果你考虑一下,改变地图的键是没有多大意义的。关键是识别价值的因素,如果该密钥发生变化,所有投注都将被取消。

映射在插入时插入给定哈希码的值。当您稍后搜索它时,它使用参数的哈希码来确定它是否是命中。我想你已经发现,如果你插入了list1,你会看到“true”打印出来,因为list2.hashCode()会生成与list1相同的哈希码插入

答案 2 :(得分:1)

这是因为HashMap使用hashCode() Object方法结合equals(Object obj)来检查此地图是否包含对象。

请参阅:

ArrayList<Integer> a = new ArrayList<>();
a.add(1);
System.out.println(a.hashCode());
a.add(2);
System.out.println(a.hashCode());

此示例显示,ArrayList的hashCode已更改。

答案 3 :(得分:0)

您绝不应将可变对象用作hashmap中的键。

因此,当你将list1作为第3行中的键时,基本上会发生的是地图计算它的hashCode,稍后它将在containsKey(someKey)中进行比较。 但是当你在第5行突变了list1时,它的hashCode基本上被改变了。 所以,如果你现在做

System.out.println(a.containsKey(list1)); 
在第5行之后

会说false 如果你做System.out.println(a.get(list1));

它会说null比较两个不同的hashCodes

答案 4 :(得分:-2)

可能你没有覆盖equals()和hashCode()方法。