一尘不染

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

java

在编写单元测试时,我经常遇到这样的情况equals(),即测试中的某些对象的assertEquals工作方式与实际环境中的工作方式不同。以一些接口为例ReportConfig。它具有id和其他几个领域。逻辑上,一个配置等于ids匹配时的另一个配置。但是,当谈到测试某些特定的实现时,例如XmlReportConfig,显然我想匹配
所有 字段。一种解决方案是不要equals在测试中使用,而只是遍历对象的属性或字段并进行比较,但这似乎不是一个好的解决方案。

因此,除了这种特定类型的情况外,我想从语义上而非技术上梳理实现平等的最佳实践。


阅读 234

收藏
2020-12-03

共1个答案

一尘不染

在语义上而非技术上,实现最佳实践的最佳方法是平等。

在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字段,则第2次fooSet.add(...)应该 没有
其他元素添加到Set,但应该返回false,因为foo1foo2具有相同id。如果定义Foo.equals(和hashCode)方法包括
idstuff领域那么这可能会被打破,因为Set可能含有具有相同id字段2点的参考对象。

如果您没有将对象存储在Collection(或Map)中,则不必以equals这种方式定义方法,但是许多人认为它是错误的形式。如果将来您
确实 将其存储在a中,Collection那么事情将会崩溃。

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

然后,您将执行以下操作:

assertTrue(obj1.equalsAllFields(obj2));

另外,一种适当的做法是 不要
定义equals考虑可变字段的方法。当我们开始谈论类层次结构时,这个问题也变得很困难。如果子对象定义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:根据可变字段定义相等项”部分:

如何用Java编写平等方法

一些其他链接:

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

2020-12-03