在我的项目中我使用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)
都是真的。
我无法理解为什么会发生这种情况,指的是这两个对象相等且具有相同的哈希码。有谁知道是什么原因,如果有任何其他类似的结构,允许键的变异?感谢。
答案 0 :(得分:4)
可变键始终是个问题。如果突变可能改变其哈希码和/或equals()
的结果,则认为密钥是可变的。话虽这么说,列表经常生成他们的哈希码并根据他们的元素检查相等性,因此他们几乎从不是地图键的好选择。
你的例子中有什么问题?添加密钥时,它是一个空列表,因此产生的哈希码与包含元素时的哈希码不同。因此,即使在更改密钥列表之后密钥和list2
的哈希码相同,您也找不到该元素。为什么?只是因为地图看起来错了。
示例(简化):
让我们从一些假设开始:
如果现在添加空列表,由于其哈希码,它会被插入到桶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()方法。