【问题标题】:Necessity of Identity Test in equals methodequals方法中身份测试的必要性
【发布时间】:2013-05-30 18:51:26
【问题描述】:

编辑:问题的核心

身份测试何时会通过,而传统的 equals 方法的其余部分何时会失败?这是为了节省做额外工作的时间而添加的吗?


原帖

我在我正在测试的课程中使用来自org.apache.commons.lang3.builder.CompareToBuilder 的CompareToBuilder。我注意到 EqualsBuilder 需要在调用 equals 构建器之前显式调用以下代码。

if (obj == this) { return true; } // Identity test

这样的逻辑也出现在 Eclipse 自动生成的 equals 方法中。我试图通过让我的equals 方法简单地调用我的compareTo 方法并测试与0 的等效性来使用DRY 方法。

一个问题是我是否需要在我的 equals 方法中包含上面的代码,将它添加到我的 compareTo 方法中,或者它是否已经被 CompareToBuilder 覆盖。我注意到CompareToBuilder 检查传递的参数的等价性,但没有收到对原始 lhs(this)和 rhs(Object obj)的任何直接引用。这让我认为这是我应该在 compareTo 方法中纠正的疏忽。

我最大的问题是我似乎无法设计一个潜在的测试用例,其中obj == this 而是this.compareTo(obj) != 0。 唯一想到的是一个不正确实现的 compareTo,发送 obj如果不首先检查 this 中的相应变量是否也为空,则其实例变量之一 null 可能会返回一个非零数。 (昨天碰到这个)。

示例等于方法:

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

示例 compareTo 方法

@Override
public int compareTo(MyClass other) {
return new CompareToBuilder()
    .append(this.getParam1(), other.getParam1())
    .append(this.getParam2(), other.getParam2())
    .toComparison();
}

【问题讨论】:

  • 困惑。如果obj==this 你希望compareTo 为0。它是同一个对象。
  • 理想情况下,这是最终目标。可以肯定的是,我想尝试通过传递一些无法给出正确响应的东西来破坏代码,除非if (obj == this) { return true; } 在那里。
  • 值得注意的是,我故意省略了对appendSuper 的调用,因为MyClass 的超类不会覆盖equals 或compareTo,我无能为力它。 (不同的开发团队)。

标签: java equals apache-commons compareto


【解决方案1】:

查看Comparable、compareTo() 的文档需要这样做

x.compareTo(y) == -y.compareTo(x)

因此,如果x == y、x.compareTo(x) == -x.compareTo(x) 和因此x.compareTo(x) == 0。所以唯一的办法是让x == y && x.compareTo(y) != 0 打破compareTo() 合约。

请注意,据我了解,obj == this 测试倾向于添加到equals() 方法中,作为一种优化,以防止在不必要的情况下评估深度比较并且不影响调用结果。 (参见 Effective Java 第 2 版,第 8 条。)我假设这里的 compareTo() 方法也是如此。

【讨论】:

  • 精彩的崩溃!我喜欢使用 compareTo 合约来说明这一点。
【解决方案2】:

身份测试何时会通过,而传统的 equals 方法的其余部分何时会失败?这是为了节省做额外工作的时间而添加的吗?

简短的回答是:从不。

身份测试是一种廉价的优化,旨在快速摆脱equals()方法的其余部分。事实上,Joshua Bloch 建议每个 equals() 方法都从优化之类的测试开始(Effective Java,第 2 版)。

先生。 Bloch 建议 equals() 采用以下通用结构:

  1. 使用 == 运算符检查参数是否是对该对象的引用。如果是,则返回 true。

  2. 使用 instanceof 运算符检查参数的类型是否正确。如果不是,则返回 false。

  3. 将参数转换为正确的类型。

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

  5. 等等

【讨论】:

  • 刚刚阅读了一篇讨论在 equals 方法中使用 instanceof 的启发性文章。长话短说,除非类/方法是最终的,否则它可能会违反平等合同。最好使用 getClass() == other.getClass()。她在文章中特别提到了布洛赫先生的例子。 angelikalanger.com/Articles/JavaSolutions/SecretsOfEquals/…
  • 除上述异议外,感谢您确认这只是优化。
  • 我不反对。布洛赫涵盖了其中一些相同的观点。事实上,我责怪 equals() 破坏了强可遗传子类的无缝继承。向子类添加新的 value 元素会破坏 equals() 契约,即使对于具有强 is-a 关系的类,例如在 Effective Java 中用作示例的 Point 和 ColorPoint 类......以及整个“优先组合优于继承” schtick无助于我不讨厌 equals()。
  • 大学计算机科学课程 5 年,高中 3 年,今天是我第一次听说这些书。在几个网站上多次阅读。我开始拥有你——完全- 错过了船的感觉。
  • 布洛赫一开始就在那里,并参与了该语言的高水平开发。读他的书很难不认为他有一个非常权威的声音。可能很少有人对 Java 有如此深入的了解。他的书与其他类似的书不同,因为它强调真正的、有用的、实用的建议,你几乎可以立即投入使用。而且你无法逃避他真的、真的、非常了解他在说什么的感觉。
【解决方案3】:

当传统的 equals 失败时,身份测试何时通过?

从技术上讲,如果“传统”也意味着“天真”,那么其中包含循环的对象图(例如,A 有一个指向 B 的 ivar 有一个指向 A 的 ivar)将通过身份测试,但是传统的 equals 实现会进入无限循环。

我不确定 Apache 库是否可以防止这种情况发生。

【讨论】:

  • @AnthonyW - 确实如此。对此感到抱歉 - 已修复。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多