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

2022-09-03 12:56:24

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

因此,除了这种特定类型的情况之外,我想弄清楚在语义上而不是技术上实现平等的最佳实践是什么。


答案 1

什么是实现平等的最佳实践,语义上,而不是技术上。

在Java中,该方法确实应该被认为是“身份平等”,因为它如何与实现集成。请考虑以下事项:equalsCollectionMap

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

如果标识是字段,则第 2 个元素不应向 添加另一个元素,而应返回 from 并具有相同的 。如果您定义(和 hashCode)方法以同时包含 字段和字段,则可能会中断此字段,因为可能包含对具有相同 id 字段的对象的 2 个引用。FooidfooSet.add(...)Setfalsefoo1foo2idFoo.equalsidstuffSet

如果您没有将对象存储在(或)中,则不必以这种方式定义方法,但是许多人认为它是不好的形式。如果将来你确实将其存储在一个中,那么事情就会被破坏。CollectionMapequalsCollection

如果我需要测试所有字段的相等性,我倾向于编写另一种方法。类似或类似的东西。equalsAllFields(Object obj)

然后你会做这样的事情:

assertTrue(obj1.equalsAllFields(obj2));

此外,适当的做法是不定义考虑可变字段的方法。当我们开始谈论类层次结构时,这个问题也变得困难。如果子对象定义为其局部字段基类的组合,则违反了其对称性:equalsequalsequals

 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:根据可变字段定义相等”部分:

如何在Java中编写相等方法

一些额外的链接:

哦,只是为了后代,无论你选择比较哪些字段来确定相等性,你都需要在计算中使用相同的字段。 并且必须是对称的。如果两个对象相等,则它们必须具有相同的哈希代码。反之亦然。hashCodeequalshashCode


答案 2

从 javadoc 复制:Object.equals(Object obj)

指示某个其他对象是否“等于”此对象。

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

  • 它是自反的:对于任何非空引用值 x,x.equals(x) 应返回 true。
  • 它是对称的:对于任何非空引用值 x 和 y,x.equals(y) 应返回 true 当且仅当 y.equals(x) 返回 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。

这对我来说很清楚,这就是平等应该如何工作。至于选择哪些字段,您可以选择所需的任何字段组合,以确定某些其他对象是否“等于”此对象

至于你的具体情况,如果你在测试中需要更广泛的平等范围,那么你就在测试中实现它。你不应该只是为了让它适合而破解你的平等方法。