使用compareTo实现equals方法

13

一般问题:在Java中实现重写默认的equals方法时,我是否应该考虑仅利用已实现的compareTo方法而不是将独立逻辑编写到equals方法中? 我注意到另一个问题中有人提到foo.equals((String)null)返回false,而String.compareTo((String)null)会抛出NullPointerException。 是什么使得这些不一致的结果成为理想的功能?

示例equals方法:

@Override
public boolean equals(Object obj) {
    if (obj != null && obj instanceof MyClass) {
        MyClass msg = (MyClass)obj;
        return this.compareTo(msg) == 0;
    }
    return false;
}

编辑:Comparable文档中引用:

类C的自然排序被认为是与等于一致的,当且仅当对于类C的每个e1和e2,e1.compareTo(e2) == 0具有与e1.equals(e2)相同的布尔值。请注意,空值不是任何类的实例,即使e.equals(null)返回false,e.compareTo(null)也应抛出NullPointerException异常

编辑:

进一步审查后,我发现在Comparable文档中还陈述了以下内容:

实现者必须确保对于所有的x和y,sgn(x.compareTo(y)) == -sgn(y.compareTo(x))。(这意味着,如果y.compareTo(x)抛出异常,则x.compareTo(y)必须抛出异常。)

因此,由于null.compareTo(x)明显会抛出NPE,所以x.compareTo(null)也应该抛出NPE。而对于equals方法,情况并非总是如此。我非常注重NPE的正确处理,因此我认为这相当重要。


4
请注意,理想情况下,.compareTo() 应该与 .equals() 一致,但这绝非保证。例如,可以参考 BigDecimal 类。是的,如果你选择使用 null 处理,可能会遇到问题。 - fge
Double.NaN对于equals(Object)compareTo(Double)都有令人惊讶的行为。NaN.equals(NaN)true,但NaN == NaNfalse。同样地,在compareTo调用中NaN是最大的Double,且NaN.compareTo(NaN) == 0equalscompareTo一致,但在这种情况下,Double对象不遵循IEEE标准。必要,但令人惊讶。 - Eric Jablow
2个回答

10

compareTo 可能会涉及比获取相等答案所需的更多工作,这可能会成为性能问题,具体取决于您的应用程序使用情况。

除此之外,遵循DRY原则,按照您的建议重新使用代码是个好主意。


关于潜在性能的好提示。尽管我对NPE大声抱怨,但我忽略了我的建议代码已经正确处理它们,假设它们在compareTo中被正确执行。那么唯一剩下的问题就是性能。 - AnthonyW

7
equals()compareTo()的区别在于equals()只是检查两个对象是否相等,而compareTo()用于识别指定类的实例的自然顺序。此外,equals()方法与hashCode()方法具有合同,但compareTo()没有。JavaDoc中指出:

请注意,null不是任何类的实例,e.compareTo(null)应抛出一个NullPointerException,即使e.equals(null)返回false。

强烈建议(但不是严格要求)(x.compareTo(y)==0)== (x.equals(y))。通常情况下,任何实现了Comparable接口并违反此条件的类都应明确指出这一事实。推荐使用的语言是“注意:此类具有与equals不一致的自然排序。”

您可以随意在equals()方法中重新使用compareTo()方法逻辑,但请注意equals()hashCode()compareTo()方法的所有契约和JavaDoc的合同。如果它们彼此之间不冲突,则可以继续。

我认为执行合同更重要。

1
“我认为合同的执行是更重要的一点。我完全同意你的观点。感谢你强调这些方法确实具有明确的预期功能。” - AnthonyW
虽然性能始终是一个有效的关注点,但强制合同似乎可以在长期内节省我的测试用例编写工作。感谢您介绍这个小技巧给我。 - AnthonyW

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接