7

直接来自这个java文档:

此禁令的一个特殊情况是不允许映射包含自己作为键。虽然允许映射将自身包含为一个值,但建议格外小心:equals 和 hashCode 方法不再在此类映射上得到很好的定义。

为什么哈希码和等号不再在这样的地图上得到很好的定义?

4

4 回答 4

5

大多数 Map 实现使用的相关部分形式 AbstractMap.equals :

            Iterator<Entry<K,V>> i = entrySet().iterator();
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                K key = e.getKey();
                V value = e.getValue();
                if (value == null) {
                    if (!(m.get(key)==null && m.containsKey(key)))
                        return false;
                } else {
                    if (!value.equals(m.get(key))) // would call equals on itself.
                        return false;
                }
            }

将地图添加为值将导致无限循环。

于 2013-07-05T11:05:13.470 回答
2

Java Docs 中该段落的完整引用是:

注意:如果将可变对象用作映射键,则必须非常小心。如果对象的值以影响等于比较的方式更改,而对象是映射中的键,则不指定映射的行为。此禁令的一个特殊情况是不允许映射包含自己作为键。虽然允许映射将自身作为值包含在内,但建议格外小心:equals 和 hashCode 方法不再在此类映射上得到很好的定义。

AbstractMap.hashCode() 方法使用映射中键值对的哈希码来计算哈希码。因此,每次修改地图时,此方法生成的哈希码都会发生变化。

哈希码用于计算存储桶以放置新条目。如果地图被用作其自身的键,那么每次更新/删除/修改新条目时,计算的存储桶都会有所不同。因此,将来使用映射作为键的查找很可能会失败,因为根据哈希码计算了不同的存储桶。未来的 puts 可能无法检测到该键已存在于映射中,然后允许多个条目具有相同的键(但在不同的存储桶中)

于 2013-07-05T11:42:07.280 回答
1

如果相同的键映射相同的值,则两个映射相等。(在某些实现中。)所以要检查相等性,应该检查每个成员的相等性。

因此,如果映射包含自身,您将获得无限递归的相等检查。

散列也是如此,因为这些可以根据映射中元素的散列来计算。

例子:

Map<Int, Object> ma;
Map<Int, Object> mb;
Map<Int, Object> mc;

ma.put(1, ma);
ma.put(2, mb);
mc.put(1, ma);
mc.put(2, mb);

作为人类,我们可以从定义中看到ma和平等。mc计算机会在两个地图中看到 mb 上的 2 个地图(一个空地图),这很好。它会在 mc 和 ma 的另一张地图上看到 1 张地图。它检查这些映射是否相等。为了确定这一点,它再次检查 1 的两个值是否相等。然后再次。

请注意,并非所有实现都是这种情况。一些实现可能会检查对象保存在内存中的位置是否相等,...但是每个递归检查都将无限循环。

于 2013-07-05T11:03:59.387 回答
0

试图解释它:

equals 方法将遍历两个 Map 并调用 map 的每个键和值的 equals 方法。因此,如果地图包含自身,您将无限期地继续调用 equals 方法。

哈希码也会发生同样的事情。

来源:类 AbstractMap 的源代码

于 2013-07-05T11:07:02.063 回答