Java equals()应该是递归的/深层的吗?

Java equals()应该是递归的/深层的吗?,java,equals,Java,Equals,没有人真正谈论equals()和hasCode()的这一方面,但这可能会对equals()和hashCode()行为产生巨大影响。当处理引用其他对象的稍微复杂的对象时,会产生大量的数据 Joshua Bloch在他的高效Java中甚至没有在他的“重写equals()方法”一章中提到它。他所有的例子都是一些琐碎的东西,比如Point和ColorPoint,它们都只有原始或接近原始的类型 递归性可以避免吗?有时很难。假设: Person { String name; Address

没有人真正谈论equals()和hasCode()的这一方面,但这可能会对equals()和hashCode()行为产生巨大影响。当处理引用其他对象的稍微复杂的对象时,会产生大量的数据

Joshua Bloch在他的高效Java中甚至没有在他的“重写equals()方法”一章中提到它。他所有的例子都是一些琐碎的东西,比如Point和ColorPoint,它们都只有原始或接近原始的类型

递归性可以避免吗?有时很难。假设:

Person {
    String name;
    Address address;
}
这两个字段都必须转到业务键(正如Hibernate的人所称),它们都是价值组件(正如Joshua Bloch所说)。地址本身就是一个复杂的对象。递归

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

一个问题是您可能会得到一个堆栈溢出错误。这是我的。
所需要的只是类将另一个对象作为“值组件”,形成一个对象图和推荐的equals()实现。是的,在这个循环中你们需要一个图表,但这并不是不现实的(想象分子,地图上的路径,相互关联的事务…)

另一个问题是性能问题。equals()的建议实际上是比较两个对象图,可能是巨大的图,最终可能会比较数千个节点而不知道。并不是所有的记忆都是必需的!考虑一些对象可能是懒惰加载的。在一个equals()或hashCode()调用中,可以加载数据库的一半

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

理想情况下,equals()方法应该测试逻辑等式。在某些情况下,它可能比物理物体下降得更深,而在另一些情况下,它可能不会

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

布洛赫确实说过:

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


至少有两个逻辑问题,对任何类型的任何两个参考都有意义,在不同的时间,
equals
可以测试这两个问题:

  • 类型能否保证这两个引用将永远标识等效对象

  • 只要保存引用的代码既不修改对象,也不将对象暴露给将要修改的代码,类型是否可以保证这两个引用将标识等价的对象

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

    不幸的是,有一种非常常见的情况,这两个问题的答案有分歧:当对可变类型对象的唯一现存引用由代码持有时,代码知道对这些对象的引用永远不会由可能对它们进行变异或测试它们的引用标识的代码持有。在这种情况下,如果两个这样的对象当前封装相同的状态,它们将永远更多地封装相同的状态,因此等价应该基于成分的等价,而不是基于引用标识。换句话说,
    等于
    应该基于嵌套对象如何回答第二个问题


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

    那么如何防止例如堆栈溢出