2017-08-31 83 views
0

在项目中,我使用的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)是真实的。

我不明白为什么会发生,指的是事实,两个对象是相等的,并具有相同的哈希码。有谁知道这是什么原因,并且如果有其他类似的结构可以让钥匙变形?谢谢。

+4

不要使用可变值hashmap键。我认为它在HashMap的文档中说过,但目前我找不到它。 –

+0

我知道我不应该(关于我已经展示的例子),但我不明白原因(对象是平等的,并具有相同的哈希代码),我正在寻找一个类似的结构,允许。 – RanSch

+1

“......如果有其他类似的结构可以让钥匙变形?” - 钥匙(至少是形成钥匙的部件)从来不会变得可变,因为那样会违反它们的使用。从现实世界的角度来看它:如果你改变你的钥匙,它不会再打开锁。如果你改变锁定,你需要一个新的密钥。 – Thomas

回答

3

可变键总是一个问题。如果突变可能改变它们的散列码和/或equals()的结果,则键被认为是可变的。也就是说,列表通常会生成他们的哈希码并根据它们的元素检查相等性,所以它们几乎从来都不是地图关键字的好选择。

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

实施例(简化):

让我们先从一些假设:

  • 空列表,如果该列表包含它返回哈希码元件5返回的0
  • 的哈希码5
  • 我们的地图具有16个桶
  • 铲斗索引由散列码%16(我们的桶的数量)
  • 确定(默认)

如果你现在它被插入到桶0空单增加,由于其哈希码。

当你做list1查找它看起来斗5由于5哈希码自那桶是空的什么都不会被发现。

的问题是,你的密钥列表改变其哈希码,因此应该被置于不同的水桶,但地图并不知道发生这种情况(以及这样做可能会导致一堆其他问题)。

+0

非常感谢,我现在可以理解它。 – RanSch

-2

也许你没有重载equals()和hashCode()方法。

+0

它们已实施。 – RanSch

1

这是因为HashMap使用hashCode()对象方法结合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发生了变化。

+0

啊,我明白了。谢谢。你知道是否有类似的数据结构允许关键突变? – RanSch

+0

@RanSch也许['IdentityHashMap'](http://docs.oracle.com/javase/7/docs/api/java/util/IdentityHashMap.html)适合您? – Thomas

+0

@Thomas不,因为我需要“等于”检查,但谢谢。 – RanSch

3

根据的Javadoc Map

注意:如果使用可变对象作为地图 按键很大,一定要小心。如果对象 的值以影响等于比较的方式进行更改,而 对象是地图中的关键字,则未指定地图的行为。这种禁令的一个特例是 地图不允许自己作为关键字。尽管 对于包含自身值的地图是允许的,但建议您谨慎使用 :在这样的地图上,不再定义equals和hashCode方法 。

你的清单是钥匙,你正在改变它们。如果列表中的内容不是,那么这将不会成为问题什么决定哈希码的值和什么是平等的,但这不是你的情况。如果你仔细想想,改变地图的关键是没有什么意义的。关键在于识别价值,如果该密钥更改,所有投注都关闭。

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

0

你不应该在你的散列表中使用一个可变对象作为键。

那么,当你把list1作为第三行中的键时,基本上会发生什么,就是map计算它的hashCode,它将在稍后在containsKey(someKey)中进行比较。 但是当u突变第5行中的list1时,其hashCode实质上发生了改变。 所以如果u现在要做的

System.out.println(a.containsKey(list1)); 

5号线后,会说false 和如果u做System.out.println(a.get(list1));

它会说null作为其比较两个不同的散列码