直接来自这个java文档:
此禁令的一个特殊情况是不允许映射包含自己作为键。虽然允许映射将自身包含为一个值,但建议格外小心:equals 和 hashCode 方法不再在此类映射上得到很好的定义。
为什么哈希码和等号不再在这样的地图上得到很好的定义?
直接来自这个java文档:
此禁令的一个特殊情况是不允许映射包含自己作为键。虽然允许映射将自身包含为一个值,但建议格外小心:equals 和 hashCode 方法不再在此类映射上得到很好的定义。
为什么哈希码和等号不再在这样的地图上得到很好的定义?
大多数 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;
}
}
将地图添加为值将导致无限循环。
Java Docs 中该段落的完整引用是:
注意:如果将可变对象用作映射键,则必须非常小心。如果对象的值以影响等于比较的方式更改,而对象是映射中的键,则不指定映射的行为。此禁令的一个特殊情况是不允许映射包含自己作为键。虽然允许映射将自身作为值包含在内,但建议格外小心:equals 和 hashCode 方法不再在此类映射上得到很好的定义。
AbstractMap.hashCode() 方法使用映射中键值对的哈希码来计算哈希码。因此,每次修改地图时,此方法生成的哈希码都会发生变化。
哈希码用于计算存储桶以放置新条目。如果地图被用作其自身的键,那么每次更新/删除/修改新条目时,计算的存储桶都会有所不同。因此,将来使用映射作为键的查找很可能会失败,因为根据哈希码计算了不同的存储桶。未来的 puts 可能无法检测到该键已存在于映射中,然后允许多个条目具有相同的键(但在不同的存储桶中)
如果相同的键映射相同的值,则两个映射相等。(在某些实现中。)所以要检查相等性,应该检查每个成员的相等性。
因此,如果映射包含自身,您将获得无限递归的相等检查。
散列也是如此,因为这些可以根据映射中元素的散列来计算。
例子:
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 的两个值是否相等。然后再次。
请注意,并非所有实现都是这种情况。一些实现可能会检查对象保存在内存中的位置是否相等,...但是每个递归检查都将无限循环。
试图解释它:
equals 方法将遍历两个 Map 并调用 map 的每个键和值的 equals 方法。因此,如果地图包含自身,您将无限期地继续调用 equals 方法。
哈希码也会发生同样的事情。
来源:类 AbstractMap 的源代码