为什么 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 是一个不同的值,但它可能会映射到同一个桶(在最坏的情况下,只考虑映射中的一个桶)。在那种情况下,您最终会在列表中得到两个相等的键,尽管它们之前并不相等。
  • 您可能没有意识到列表的更改,因此可能不会使用相等列表进行查找,即使您认为自己是。