在编写单元测试时,我经常面对equals()
测试中某些对象(在assertEquals
中)应该与它在实际环境中的工作方式不同的情况。以一些接口ReportConfig
为例。它有id
和其他几个领域。逻辑上,当id
s匹配时,一个配置等于另一个配置。但是当谈到测试一些特定的实现时,比如XmlReportConfig
,显然我想要匹配所有字段。一种解决方案是不在测试中使用equals
,只是迭代对象属性或字段并进行比较,但它似乎不是一个好的解决方案。
因此,除了这种特定类型的情况之外,我想要理清什么是在语义上而不是在技术上实现平等的最佳实践。
什么是实现等同的最佳实践,在语义上,而不是在技术上。
在Java中,equals
方法确实应该被认为是"identity 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(...)
不应该添加另一个元素到Set
但应该返回false
,因为foo1
和foo2
有相同的id
。如果您定义Foo.equals
(和hashCode)方法以包含id
和stuff
字段,那么这可能会被破坏,因为Set
可能包含对具有相同id字段的对象的2个引用。
如果你没有将你的对象存储在Collection
(或Map
)中,那么你不必以这种方式定义equals
方法,但是许多人认为它是不好的形式。如果将来你把它存放在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:在可变字段方面定义平等”部分:
一些额外的链接:
哦,只是为了子孙后代,无论你选择哪些字段来比较以确定相等性,你都需要在hashCode
计算中使用相同的字段。 equals
和hashCode
必须是对称的。如果两个对象相等,则它们必须具有相同的哈希码。相反的情况不一定如此。
复制自Object.equals(Object obj)
javadoc:
指示某个其他对象是否“等于”此对象。
equals方法在非null对象引用上实现等价关系:
这对我来说非常清楚,这就是平等应该如何运作。至于选择哪个字段,您可以选择需要哪个字段组合来确定某个其他对象是否“等于”此字段。
至于你的具体情况,如果你在测试中需要更广泛的平等范围,那么你在测试中实现它。你不应该破解你的equals方法只是为了使它适合。
您应该使用所有重要变量,即其值不是来自其他变量的变量,等于。
这来自Effective Java:
对于类中的每个“重要”字段,检查参数的该字段是否与此对象的相应字段匹配。
如果你想匹配id,因为它是该类的唯一标识符,那么只需比较id值,在这种情况下不要使用equals。
如果您有唯一标识符,则该语言不允许您强制执行没有其他具有该标识符的对象或其余变量的值匹配。但是,您可以在类的文档中定义它,并且可以在equals实现或其他地方使用断言,因为它是由类的语义给出的不变量。
在编写equals()
时,我不会考虑单元测试。
您可以通过实现equals()
和hashcode()
来定义每个对象与一个或一组属性的相等性。
在您的测试中,如果要比较对象的所有属性,那么显然您需要调用每个方法。
我认为最好单独对待它们。
我认为覆盖equals()
方法的唯一最佳做法是常识。
除了Java API的equivalence definition之外,没有规则。一旦选择了相等的定义,就必须将它应用于hashCode()
方法。
我的意思是,作为开发人员,你,你的同事和维护者应该知道你的实例是什么时候说Watermelon
等于另一个Object实例。