【问题标题】:Should mutable collections override equals and hashCode?可变集合应该覆盖equals和hashCode吗?
【发布时间】:2011-07-11 22:46:33
【问题描述】:

我只是想知道为可变集合覆盖 equalshashCode 是否是个好主意。这意味着如果我将这样的集合插入到 HashSet 中,然后修改该集合,HashSet 将不再能够找到该集合。这是否意味着只有不可变的集合才应该覆盖 equalshashCode,还是 Java 程序员只能忍受这种麻烦?

【问题讨论】:

  • 在 OO 层次结构的顶部存在 equalshashCode 是 Java 程序员必须忍受的麻烦。 equalshashCode 本质上是坏的,但不仅仅是因为你提到的原因。我主要在 Java 中工作,做 "OO over immutable objects",即使这样做,equals 和 hashCode 也被破坏了。在大多数情况下,拥有这些方法是没有意义的:不可能满足任何非最终类的 equalshashCode 协定。这在 Effective Java 中有很好的解释。
  • 在这里查看我的相关问题,10 次赞成 + 几个最喜欢的:stackoverflow.com/questions/2205565 equalshashCode 除了最简单的情况外,其他任何事情都无法解决您只将一种类型的 immutable 对象(没有继承,没有任何东西)放在集合中。你的案子不是一个简单的案子,你会有很多问题。 OO 层次结构顶部的 equalshashCode 是一个错误,简单明了(但大多数 Java 程序员没有意识到)。
  • @SyntaxT3rr0r - 有一些方法可以在继承链中很好地处理这个问题,请参阅angelikalanger.com/Articles/JavaSolutions/SecretsOfEquals/…。此外,实体对象通常是可变的,但有一些用于这些方法的不可变“id”。所以不,它们不是“从根本上打破所有java”。像所有工具一样,您需要知道如何使用它们。
  • StringBuffer 类和 StringBuilder 类也不会因为可变性而覆盖 equals() 和 hashCode()。

标签: java collections overriding equals hashcode


【解决方案1】:

如果你的类应该表现得像一个值类型,你应该覆盖equalshashCode。集合通常不是这种情况。

(我真的没有太多Java经验。这个答案是基于C#的。)

【讨论】:

    【解决方案2】:

    深浅相等的问题比Java大;所有面向对象的语言都必​​须关注它。

    您添加到集合中的对象应该覆盖等号和哈希码,但集合接口的抽象实现中内置的默认行为足以满足集合本身。

    【讨论】:

    • @duffymo: +1... 但是所有 OO 语言都不会犯 Java 创造者犯过的错误:把 equalshashCode 在 OO 层次结构的顶部,好像它有任何意义(它没有)。应该有一个 Equalable (只是编造的)接口或其他东西。而且,不,我不是 Java 的仇恨者,而是 Java 的粉丝 :)
    • 我不同意这是一个错误。您需要所有对象的身份;这就是为什么它在 java.lang.Object 中。 “平等”?我认为这是一个错误,但这只是我。 8)
    • @duffymo:但身份与 equalshashCode 的概念无关。它只是与 OO 不兼容,仅此而已。关于 Java equals/hashCode SNAFU 有一篇很棒的文章(Bill Venners 和 Scala IIRC 的创建者之间的对话),并且再次在 “Effective Java” 中进行了解释。对于任何非最终类,它破坏了,这是事实。可变对象的概念打破了,这也是事实。在某一时刻,您不得不怀疑它们在 OO 层次结构顶部的存在是否仍然有意义。
    • @duffymo:你是什么意思?您认为 equalshashCode 与身份有关吗?怎么可能?身份问题存在于许多语言中。身份是 FP 语言中一个非常重要的概念(例如 Clojure)。但是 equalshashCode 是 Java 特性,对可变性没有意义,对非最终类更没有意义。当然,还有一些方法可以在 Java 中使用“身份”概念,而无需使用 equals 和 hashCode...
    • 五小时能量对你来说已经足够了,SyntaxT3rr0r。放松。可以写 equals 和 hashCode 与身份一致;对于 Hibernate 和持久对象,这是推荐的最佳实践。你的观点很好,但我通过浏览器获得的交付有点刺耳。
    【解决方案3】:

    这与任何可变类相同。当你将一个实例插入到一个 HashSet 中,然后调用一个 mutating 方法时,你会遇到麻烦。所以,我的回答是:是的,如果有用的话。

    您当然可以在将集合添加到 HashSet 之前为您的集合使用不可变的 Wrapper。

    【讨论】:

      【解决方案4】:

      我认为更大的问题是,如果有人尝试将您的 FredCollection 的实例添加到 Set 中两次,会发生什么。

      FredCollection c = ...
      set.add(c);
      set.add(c);
      

      这之后setsize() 应该是2 还是1

      您是否需要测试FredCollection 的两个不同实例的“相等性”?我认为这个问题的答案对于确定您的equals()/hashcode() 行为比其他任何事情都更重要。

      【讨论】:

      • 能否提供一个工作示例的插图,以便进一步参考。
      • size() 将为 1,因为一般来说,一个 Set 只包含一个对象。 c 未分配新值。即使你调用了 c.add('cow') 它仍然是同一个对象 c。默认的 equals() 是 == 的浅比较。
      【解决方案5】:

      这不仅是集合的问题,而且是一般可变对象的问题(另一个示例:Point2D)。是的,这是 Java 程序员最终学会考虑的潜在问题。

      【讨论】:

        【解决方案6】:

        您不应该覆盖 equals 和 hashCode 以便它们反映可变成员。

        更多的是我个人的观点。我认为哈希码和等号是不应该用于实现业务逻辑的技术术语。想象一下:你有两个 Objects(不仅仅是 Collections)并询问它们是否相等,那么有两种不同的方式来回答它们:

        • 技术:如果它们表示同一个对象,则它们是相等的,这不同于作为同一个对象(如果你想到代理、序列化、远程的东西.. .)
        • 业务逻辑:如果看起来相同(相同的属性),它们是相等的——这里重要的是,即使在一个应用程序中,对于同一个类也没有神圣的平等定义。 (示例问题:两块石头何时相等?))

        但是因为equals被技术的东西(HashMap)使用,你应该以技术的方式实现它,并通过其他东西(比如比较器接口)构建与equals相关的业务逻辑。对于您的收藏,这意味着:不要覆盖 equals 和 hashCode(以违反技术合同的方式:

        注意:如果将可变对象用作映射,则必须非常小心 键。如果对象的值未指定映射的行为 以影响等于比较的方式进行更改,而 该对象是地图中的一个键。

        (地图的java文档) )。

        【讨论】:

        • 拒绝对 Java API 的错误引用,这可能会严重混淆阅读本文的人。实际的完整引用是(强调我的):“在 Java 应用程序执行期间,只要在同一个对象上多次调用它,hashCode 方法必须始终返回相同的整数,没有提供任何信息在equals比较中使用的对象被修改了。" 所以只要可变成员也在equals中比较,就可以在hashCode中使用。为可变对象实现 equals 和 hashCode 是否是好的做法是另一回事。
        • 取消投票,谢谢。不过,我认为将 equals 用于业务逻辑没有问题,只要您知道其中的含义 - 有时您想知道例如一组人包含某个人,即使您没有确切的对象(您可能已经从数据库中读取了两者)。也许问题在于,“equals”建议检查相等的值(我们称之为“业务相等”),而实际的想法是检查两个对象是否表示相同的事物(“业务标识”)。
        【解决方案7】:

        equalshashCode 的一个基本困难是,有两种逻辑方式可以定义等价关系;一个类的一些消费者想要一个定义,而同一类的其他消费者想要另一个。

        我将两个等价关系定义如下:

        • 如果用对 Y 的引用覆盖 X 不会改变 X 或 Y 的任何成员的当前或未来行为,则两个对象引用 X 和 Y 完全等效。

        • 两个对象引用 X 和 Y 具有相同的状态,如果在一个没有持久化从身份相关哈希函数返回的值的程序中,将所有对 X 的引用与对 Y 的所有引用交换将使程序状态保持不变。

        请注意,第二个定义主要与常见场景相关,其中两个事物持有对某些可变类型(例如数组)的对象的引用,但可以确定,至少在某些特定的感兴趣的时间范围内,那些对象不会暴露于任何可能改变它们的东西。在这种情况下,如果“持有者”对象在所有其他方面都是等价的,那么它们的等价性应该取决于它们所持有的对象是否满足上述等价的第二定义。

        请注意,第二个定义本身并不涉及对象状态可能如何变化的任何细节。进一步注意,对于等价的定义,不可变对象可以报告具有相同内容的不同对象为相等或不等(如果 X 和 Y 不同的唯一方式是 X.Equals(X) 报告true 而 X.Equals(Y) 报告 false,这会有所不同,但让此类对象对第一个等价关系使用引用标识并在第二个等价关系中使用其他方面的等价可能是最有用的。

        不幸的是,因为 Java 只提供了一对等价定义类,所以类设计者必须猜测哪个等价定义与类的使用者最相关。虽然有充分的理由支持始终使用第一个,但第二个通常更实际有用。第二个最大的问题是一个类无法知道使用该类的代码何时需要第一个等价关系。

        【讨论】:

          【解决方案8】:

          equals 用于在 CopyOnWriteArraySet、HashSet 等集合中添加/删除元素,如果两个不同对象的 hashCode 相等等。equals 需要是对称的,即如果 B.equals(C) 返回 true,则 C.equals(B)应该返回相同的结果。否则,您对这些 XXXSet 的添加/删除行为会令人困惑。检查 Overriding equals for CopyOnWriteArraySet.add and remove 以了解不正确的 equals 覆盖如何影响集合上的添加/删除操作

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2016-05-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-09-06
            相关资源
            最近更新 更多