7
    ArrayList<Integer> lis = new ArrayList<Integer>();
    lis.add(2);
    lis.add(3);
    ArrayList<Integer> lis2 = new ArrayList<Integer>();
    lis2.add(2);
    lis2.add(3);
    HashMap<ArrayList<Integer>, Integer> map = new HashMap<ArrayList<Integer>, Integer>();
    map.put(lis, 7);
    System.out.println(map.containsKey(lis2));

最初,我希望代码打印出“假”,因为 lis 和 lis2 是不同的对象。令人惊讶的是,代码打印出“真实”。hashmap 在调用 containsKey() 时会检查什么?

4

4 回答 4

8

它检查.hashCode以找到存储桶,然后使用.equals. 如果所有元素的顺序相同并且也是 ,则List.equals返回。将为具有相同元素的两个实例返回相同的值,因此它会找到正确的存储桶,然后使用并查看列表的元素相同且顺序相同。true.equalsArrayList.hashCodeArrayList.equals

例如:

ArrayList<Integer> lis = new ArrayList<Integer>();
lis.add(2);
lis.add(3);
ArrayList<Integer> lis2 = new ArrayList<Integer>();
lis2.add(2);
lis2.add(3);
System.out.println(lis.equals(lis2)); // Prints "true"

还值得注意的是,您永远不应该使用可变对象作为HashMap. 通过修改密钥,您可以导致其所在的存储桶无效。例如,如果我这样做:

map.put(lis, 7);
lis.add(3);
System.out.println(map.get(lis)); // Prints "null", *not* "7"

这是因为添加另一个元素会改变 的值lis.hashCode()。当您put列出列表时,hashCode用于挑选一个桶。通过添加新元素,您可以更改它将使用的存储桶,但不会更改您已添加到地图中的条目的存储桶。添加到上述内容:

map.put(lis, 7);
lis.add(3);
map.put(lis, 7);
System.out.println(map.size()); // Prints "2"

它第二次解析为不同的存储桶,因此将其视为第二个元素。

在这种情况下,您将使用Collections.unmodifiableList“冻结”列表,添加它,然后再也不碰它:

map.put(Collections.unmodifiableList(lis), 7);

然后,如果您致电get().add(3)

map.get(7).add(3);

这将抛出一个UnsupportedOperationException.

于 2013-10-29T07:19:59.743 回答
3

这是因为 lis 和 lis2 的hashCode相等。

lis2.hashCode() == lis.hashCode() 是真的。

源码(HashMap)中的containsKey方法如下:

/**
 * Returns <tt>true</tt> if this map contains a mapping for the
 * specified key.
 *
 * @param   key   The key whose presence in this map is to be tested
 * @return <tt>true</tt> if this map contains a mapping for the specified
 * key.
 */
public boolean containsKey(Object key) {
    Object k = maskNull(key);
    int hash = hash(k.hashCode());
    int i = indexFor(hash, table.length);
    Entry e = table[i]; 
    while (e != null) {
        if (e.hash == hash && eq(k, e.key)) 
            return true;
        e = e.next;
    }
    return false;
}
于 2013-10-29T07:23:47.810 回答
1

您将哈希映射定义为

HashMap<ArrayList<Integer>, Integer> map = new HashMap<ArrayList<Integer>, Integer>();

所以键是整数列表,值是整数。

map.containsKey(lis2)将尝试找到给定键的匹配项。因此,将在每个键上调用equals方法。由于键实际上是一个整数列表,因此将按顺序对该列表的每个项目调用 equal 方法。

这就是输出为真的原因。

如果您更改第二个列表中的任何项目,甚至更改项目的顺序,则输出将为 false。

于 2013-10-29T07:25:09.670 回答
1

map.containsKeytrue如果此映射包含指定键的映射,则返回。它使用equals的方法key来检查是否相等:

key != null && key.equals(k)

如果(从 ArrayList doc 复制),则说两个数组列表是相等的:

比较指定对象与此列表是否相等。当且仅当指定对象也是一个列表时返回 true,两个列表具有相同的大小,并且两个列表中所有对应的元素对都相等。(如果 (e1==null ? e2==null : e1.equals(e2)) 两个元素 e1 和 e2 相等。)换句话说,如果两个列表以相同的顺序包含相同的元素,则它们被定义为相等.

这个实现首先检查指定的对象是否是这个列表。如果是,则返回 true;如果不是,它检查指定的对象是否是一个列表。如果不是,则返回 false;如果是这样,它会遍历两个列表,比较相应的元素对。如果任何比较返回 false,则此方法返回 false。如果其中一个迭代器在另一个迭代器之前用完元素,则返回 false(因为列表的长度不等);否则在迭代完成时返回 true。

由于两个列表以相同的顺序包含相同数量的元素,因此它们被认为是equal,这就是为什么map.containsKey(lis2)返回true

于 2013-10-29T07:28:40.380 回答