Java 为equals()实现选择字段的最佳实践

Java 为equals()实现选择字段的最佳实践,java,equals,equality,Java,Equals,Equality,在编写单元测试时,我经常遇到这样的情况:测试中的某些对象的equals(),在assertEquals中,应该与实际环境中的工作方式不同。以某个界面ReportConfig为例。它有id和几个其他字段。从逻辑上讲,当它们的ids匹配时,一个配置等于另一个配置。但是当涉及到测试一些特定的实现时,比如说,XmlReportConfig,显然我想要匹配所有的字段。一种解决方案是在测试中不使用equals,只需迭代对象属性或字段并比较它们,但这似乎不是一个好的解决方案 因此,除了这种特定类型的情况外,我

在编写单元测试时,我经常遇到这样的情况:测试中的某些对象的
equals()
,在
assertEquals
中,应该与实际环境中的工作方式不同。以某个界面
ReportConfig
为例。它有
id
和几个其他字段。从逻辑上讲,当它们的
id
s匹配时,一个配置等于另一个配置。但是当涉及到测试一些特定的实现时,比如说,
XmlReportConfig
,显然我想要匹配所有的字段。一种解决方案是在测试中不使用
equals
,只需迭代对象属性或字段并比较它们,但这似乎不是一个好的解决方案


因此,除了这种特定类型的情况外,我想整理一下在语义上而不是技术上实现equals的最佳实践。

Object.equals(Object obj)
javadoc:

指示其他对象是否与此对象“相等”

equals方法在非null对象引用上实现等价关系:

  • 它是自反的:对于任何非空参考值x,x.equals(x)应该返回true
  • 它是对称的:对于任何非空的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)才应返回true
  • 它是可传递的:对于任何非空引用值x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)应该返回true
  • 它是一致的:对于任何非空的引用值x和y,x.equals(y)的多次调用始终返回true或false,前提是没有修改对象上equals比较中使用的信息
  • 对于任何非空引用值x,x.equals(null)应返回false
这对我来说很清楚,这就是平等应该如何发挥作用。至于要选择哪些字段,您可以选择需要的字段组合,以确定其他对象是否“等于”此对象


至于您的具体情况,如果您在测试中需要更广泛的平等范围,那么您可以在测试中实现它。你不应该仅仅为了使equals方法适合而修改它。

你应该在equals中使用所有重要变量,即其值不是从其他变量派生的变量

这来自有效的Java:

对于类中的每个“重要”字段,检查参数的该字段是否与该对象的相应字段匹配


如果您想匹配id,因为它是该类的唯一标识符,那么只需比较id值,在这种情况下不要使用equals

如果您有一个唯一的标识符,该语言不允许您强制执行没有其他具有该标识符的对象,或者其他变量的值匹配。但是,您可以在类的文档中定义它,并且可以在equals实现或其他地方使用断言,因为它是由类的语义给定的不变量

在语义上而不是技术上,实现equals的最佳实践是什么

在Java中,
equals
方法确实应该考虑,因为它是如何与
Collection
Map
实现集成的。考虑以下事项:

 public class Foo() {
    int id;
    String stuff;
 }

 Foo foo1 = new Foo(10, "stuff"); 
 fooSet.add(foo1);
 ...
 Foo foo2 = new Foo(10, "other stuff"); 
 fooSet.add(foo2);
如果
Foo
标识是
id
字段,则第二个
fooSet.add(…)
不应向
集合添加另一个元素,而是应返回
false
,因为
foo1
foo2
具有相同的
id
。如果您将
Foo.equals
(和hashCode)方法定义为同时包含
id
stuff
字段,则这可能会被破坏,因为
集合
可能包含对具有相同id字段的对象的两个引用


如果不将对象存储在
集合
(或
映射
)中,则不必以这种方式定义
等于
方法,但许多人认为它是一种糟糕的形式。如果将来你把它储存在一个
集合中
,那么东西就会坏掉

若我需要测试所有字段的相等性,我倾向于编写另一个方法。类似于
equalsAllFields(objectobj)
之类的东西

然后你会做一些类似的事情:

assertTrue(obj1.equalsAllFields(obj2));
此外,适当的做法是不要定义考虑可变字段的
等于
方法。当我们开始讨论类层次结构时,这个问题也变得很困难。如果子对象将
equals
定义为其局部字段和基类
equals
的组合,则其对称性已被破坏:

 Point p = new Point(1, 2);
 // ColoredPoint extends Point
 ColoredPoint c = new ColoredPoint(1, 2, Color.RED);
 // this is true because both points are at the location 1, 2
 assertTrue(p.equals(c));
 // however, this would return false because the Point p does not have a color
 assertFalse(c.equals(p));
我极力推荐的更多阅读资料是本伟大页面中的“陷阱3:根据可变字段定义相等”部分:

其他一些链接:


哦,为了子孙后代,无论您选择比较哪些字段来确定相等性,您都需要在
hashCode
计算中使用相同的字段<代码>等于和哈希代码必须是对称的。如果两个对象相等,则它们必须具有相同的哈希代码。相反并不一定正确。

我认为重写
equals()
方法的唯一最佳实践是常识

除了JavaAPI的定义之外,没有任何规则。一旦选择了相等的定义,就必须将其应用于
hashCode()
方法


我的意思是,作为一名开发人员,您、您的同事和维护人员应该知道您的西瓜实例何时等于另一个对象实例。

编写
equals()
时,我不会考虑单元测试

您可以使用一个或多个组定义每个对象的相等性