【问题标题】:is equals() supposed to be recursive/deep?equals() 应该是递归/深度的吗?
【发布时间】:2013-04-10 11:46:03
【问题描述】:

没有人真正谈论过 equals()hasCode() 的这一方面,但对 equals()hashCode() 行为。在处理引用其他对象的更复杂的对象时非常庞大。

Joshua Bloch 在他的 Effective Java 中甚至没有在他的“覆盖 equals() 方法”一章中提到它。他所有的例子都是像 Point 和 ColorPoint 这样的琐事,都只有原始或接近原始的类型。

可以避免递归吗?有时几乎没有。假设:

Person {
    String name;
    Address address;
}

这两个字段都必须转到 business key(正如 Hibernate 人所说),它们都是 value components(正如 Joshua Bloch 所说)。 Address 本身就是一个复杂的对象。递归。

请注意,Eclipse 和 IntelliJ 等 IDE 确实会生成递归 equals() 和 hashCode()。 默认情况下,它们使用所有字段。如果你大量使用生成器工具,你就是在自找麻烦。

一个麻烦是你会得到一个StackOverflowError。这是我的simple test proving it
所需要的只是将另一个对象作为“值组件”的类,形成一个对象图和推荐的 equals() 实现。是的,您需要在该循环中绘制图表,但这并非不切实际(想象分子、地图上的路径、相互关联的交易......)。

另一个问题是性能。 equals() 推荐的实际上是比较两个对象图,可能是巨大的图,最终可能会在不知情的情况下比较数千个节点。并不是所有的都在内存中是必需的!考虑到某些对象可能是可延迟加载的。一个 equals() 或 hashCode() 调用最终可能会加载一半的数据库。

悖论是,你越是严格地重写 equals() 和 hashCode(),你就越有可能遇到麻烦。

【问题讨论】:

    标签: java equals


    【解决方案1】:

    理想情况下,equals() 方法应该测试逻辑相等。在某些情况下,它可能比物理对象下降得更深,而在其他情况下,它可能不会。

    如果测试逻辑相等不可行,由于性能或其他问题,那么您可以保留 Object 提供的默认实现,而不依赖 equals()。例如,您不必将对象图用作集合中的键。

    布洛赫确实这么说:

    避免问题的最简单方法是不要重写 equals 方法,在这种情况下,类的每个实例都只与自身相等。

    【讨论】:

    • 那么你如何防止例如 StackOverflowError?我可以想到身份缓存,检查对象是否已经下降。但这会使 equals 方法变得非常复杂(加上防止并发访问缓存等其他问题。)
    • 如果对象具有父子关系,则不要在相等性测试中包含父对象。如果对象在任意图中,则在相等测试期间不要多次遍历同一对象(辅助相等测试可能对此有用)。
    【解决方案2】:

    至少有两个逻辑问题,这对于任何类型的任何两个引用都是有意义的,这将在不同时间对equals 进行测试:

    1. 该类型能否保证两个引用将永远标识等效对象?

    2. 只要保存引用的代码既不修改对象,也不将它们暴露给可以识别的代码,该类型能否保证这两个引用将标识等效对象?

    如果引用标识的对象可能随时更改,恕不另行通知,则唯一应视为等效的引用是标识相同对象的引用。如果引用标识了一个深度不可变类型的对象,并且从未以测试其身份的方式使用(例如锁定、IdentityHashSet 等),则所有对具有相同内容的对象的引用都应被视为等效。在上述两种情况下,equals 的正确行为是清晰明确的,因为在前一种情况下,两个问题的正确答案将通过测试参考身份获得,而在后一种情况下,正确答案将通过以下方式获得测试深度平等。

    不幸的是,有一个非常常见的场景,这两个问题的答案存在分歧:当唯一现存的对可变类型对象的引用由代码持有时,该代码知道对这些对象的引用永远不会被可能发生变异的代码持有他们也不测试他们的参考身份。在那种情况下,如果两个这样的对象目前封装了相同的状态,它们将永远这样做,因此等价应该基于成分的等价而不是引用身份。换句话说,equals 应该基于嵌套对象如何回答第二个问题。

    因为相等的含义取决于仅由引用的持有者知道的信息,而不是由引用标识的对象,所以 equals 方法实际上不可能知道哪种风格的相等是合适的.知道它们所引用的事物可能会自发改变的类型应该测试这些组成部分的引用相等性,而知道它们不会改变的类型通常应该测试深度相等性。集合之类的东西应该允许所有者指定存储在集合中的东西是否可以自发改变,并在此基础上测试相等性;不幸的是,相对较少的内置集合包含任何此类工具(代码可以在例如HashTableIdentityHashTable 之间进行选择,以区分适合键的测试类型,但大多数类型的集合没有等效的选择)。最好的办法可能是让每个新的集合类型在其构造函数中提供封装模式的选择:将集合本身​​视为可能在没有通知的情况下更改的东西(报告集合上的引用相等),假设集合将保持不变对可能在没有通知的情况下更改的事物的引用集(测试内容上的引用相等性),假设集合和组成对象都不会改变(测试每个组成对象的equals),或者——对于数组的集合或不支持深度相等测试的嵌套集合 - 执行到指定深度的超深度相等测试。

    【讨论】:

      猜你喜欢
      • 2011-05-04
      • 2017-02-20
      • 1970-01-01
      • 2015-11-22
      • 2011-02-07
      • 1970-01-01
      • 2021-10-28
      • 2015-02-28
      • 2010-10-25
      相关资源
      最近更新 更多