【问题标题】:Map containing itself as a value;包含自身作为值的映射;
【发布时间】:2013-07-05 10:58:22
【问题描述】:

直接来自thisjava doc:

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

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

【问题讨论】:

  • 如果我没记错的话,equals 方法会检查地图的内容是否相等。这是使用内容的 equals 方法完成的,即地图本身。我很确定我们最终会遇到 StackOverflowError
  • 可能是 map equals 函数的样子?也许它会导致无限循环?只是一个想法
  • @MarcoForberg 如果我没记错的话,最好检查您要比较的对象是否真的是您自己,如果是,则立即返回 true。
  • @NamSan 创建两张地图(比如说 A 和 B)并将 A 放入 B 并将 B 放入 A。现在尝试 A.equals(B)。您将获得预期的行为,并且检查引用相等性在这里没有帮助,但是顺便说一句。每当我执行equals() 时,我都会按照你说的做;)
  • @MarcoForberg 在您的示例中,它最终会产生 StackOverflowError,但在问题中给出的示例中,它不会产生 StackOverflowError

标签: java dictionary equals hashcode


【解决方案1】:

大多数 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;
                }
            }

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

【讨论】:

  • equals() 方法中的第一条语句是 if (o == this) return true; 我认为这会阻止无限循环,因为它会立即返回并且永远不要执行你突出显示的代码。
  • 该死的你是对的。答案需要不加标记。也许是因为它是如此依赖于实现,他们没有定义它不推荐它。
  • 好吧,您可以通过创建两个映射来创建问题,将一个映射放入另一个映射并在一个映射上调用 equals,另一个作为参数。或者您只需将一张地图放入自身并调用 hashcode()
【解决方案2】:

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

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

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

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

【讨论】:

    【解决方案3】:

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

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

    哈希值也是如此,因为它们可以根据地图中元素的哈希值进行计算。

    例子:

    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);
    

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

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

    【讨论】:

      【解决方案4】:

      试着解释一下:

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

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

      来源:AbstractMap类的源代码

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-02-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多