为什么 ArrayList 作为 HashMap 中的键不起作用?
why doesn't ArrayList as key in HashMap work?
我正在尝试使用 ArrayList
作为 HashMap
中的键,但如果我在将列表设置为键后向列表添加值,地图将无法识别列出了。我已经找到了解决我的问题的方法,但这是一种丑陋的方法,这里是该问题的一些示例代码:
HashMap<Object,String> hm = new HashMap<Object,String>();
List<String> l = new ArrayList<String>();
hm.put(l, "stuff");
l.add("test");//add item after adding the list to the hashmap
System.out.println(hm.get(l));
这将 return 文本 "null" 而
HashMap<Object,String> hm = new HashMap<Object,String>();
List<String> l = new ArrayList<String>();
l.add("test"); //add item before adding the list to the hashmap
hm.put(l, "stuff");
System.out.println(hm.get(l));
工作正常并且 returns "stuff"
有人知道为什么会这样吗?
简短:因为键必须是不可变的,哈希映射才能工作(至少它们的标识必须是不可变的)而列表不是。
Long:当您将键添加到映射时,其 hashCode()
方法用于确定将条目放入的存储桶。在那个 bucket 里面 equals()
用于检查那个 key 是否已经存在。查找也是如此。
现在 ArrayList
做了一个很深的 equals()
和 hashCode()
所以如果你改变列表 after 使用它作为键你会结束equals()
在不同的桶中或有不同的结果,地图很可能找不到它。
编辑
hashCode()
AbstractList
的实现(ArrayList
扩展):
public int hashCode() {
int hashCode = 1;
for (E e : this)
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
return hashCode;
}
如您所见:如果列表为空,则哈希码将为 1,否则哈希码将为其他值(在您的情况下为 31 * "test".hashCode()
)。因此,您最终可能会查找另一个失败的存储桶。
编辑 2
关于 "different outcome for equals()
" 的澄清:当然 equals()
应该 return 如果列表用作键并且列表用于查找 only 包含相同顺序的相同元素。但是,如果您在将其用作密钥后更改该列表,您可能会遇到不同的情况:
- 虽然 hashCode return 是一个不同的值,但它可能会映射到同一个桶(在最坏的情况下,只考虑映射中的一个桶)。在那种情况下,您最终会在列表中得到两个相等的键,尽管它们之前并不相等。
- 您可能没有意识到列表的更改,因此可能不会使用相等列表进行查找,即使您认为自己是。
我正在尝试使用 ArrayList
作为 HashMap
中的键,但如果我在将列表设置为键后向列表添加值,地图将无法识别列出了。我已经找到了解决我的问题的方法,但这是一种丑陋的方法,这里是该问题的一些示例代码:
HashMap<Object,String> hm = new HashMap<Object,String>();
List<String> l = new ArrayList<String>();
hm.put(l, "stuff");
l.add("test");//add item after adding the list to the hashmap
System.out.println(hm.get(l));
这将 return 文本 "null" 而
HashMap<Object,String> hm = new HashMap<Object,String>();
List<String> l = new ArrayList<String>();
l.add("test"); //add item before adding the list to the hashmap
hm.put(l, "stuff");
System.out.println(hm.get(l));
工作正常并且 returns "stuff"
有人知道为什么会这样吗?
简短:因为键必须是不可变的,哈希映射才能工作(至少它们的标识必须是不可变的)而列表不是。
Long:当您将键添加到映射时,其 hashCode()
方法用于确定将条目放入的存储桶。在那个 bucket 里面 equals()
用于检查那个 key 是否已经存在。查找也是如此。
现在 ArrayList
做了一个很深的 equals()
和 hashCode()
所以如果你改变列表 after 使用它作为键你会结束equals()
在不同的桶中或有不同的结果,地图很可能找不到它。
编辑
hashCode()
AbstractList
的实现(ArrayList
扩展):
public int hashCode() {
int hashCode = 1;
for (E e : this)
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
return hashCode;
}
如您所见:如果列表为空,则哈希码将为 1,否则哈希码将为其他值(在您的情况下为 31 * "test".hashCode()
)。因此,您最终可能会查找另一个失败的存储桶。
编辑 2
关于 "different outcome for equals()
" 的澄清:当然 equals()
应该 return 如果列表用作键并且列表用于查找 only 包含相同顺序的相同元素。但是,如果您在将其用作密钥后更改该列表,您可能会遇到不同的情况:
- 虽然 hashCode return 是一个不同的值,但它可能会映射到同一个桶(在最坏的情况下,只考虑映射中的一个桶)。在那种情况下,您最终会在列表中得到两个相等的键,尽管它们之前并不相等。
- 您可能没有意识到列表的更改,因此可能不会使用相等列表进行查找,即使您认为自己是。