【问题标题】:Recursive .equals method from call to super.equals从调用到 super.equals 的递归 .equals 方法
【发布时间】:2013-11-04 00:43:00
【问题描述】:

编辑

我做得很糟糕,并且提供了不完整的信息供任何人确定我的问题的原因。真正的问题是我在 Animal 中有一个嵌套类,它有自己的 .equals ,它在其外部类型上调用 .equals 。 (因此,从嵌套类的 .equals 中调用 .equals on animal 调用 .equals on animal)。


我在继承树中有三个类。假设他们是Animal --> Dog<Owner> --> DogWithHumanOwner

所以 DogWithHumanOwner 是通用 Dog 的一个实现,它专门为其所有者提供了一个 Human。

我已经覆盖了 Animal 和 Dog 的 .equal 方法。 Dog 的 .equal 方法如下所示:

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (!super.equals(obj))
        return false;

    [other stuff]

    return true;
}

我实际使用的是 DogWithHumanOwner。似乎当我尝试比较两个DogWithHumanOwner的相等性时,dogWithHumanOwner继承了Dog<Owner>的equals方法,它调用了super.equals,也就是dogWithHumanOwner的超类的.equals方法,也就是它所在的方法,等等它会导致递归循环和堆栈溢出。

(我不需要比较 Dog<Owner> 类的实现的任何特定属性,因为我在 Dog<Owner> 中这样做,并且所有者需要有适当的 equals 方法。)

编写避免此问题的 .equals 方法的最佳做法是什么?我在画一个空白。我应该只手动测试相等性而不调用 super 吗?

编辑:我不得不删除 BlackLabs,因为我为什么要使用 blackLabs 这样做是没有意义的。

【问题讨论】:

  • Dog 的超类应该是 Animal。调用其equals 函数不应导致递归。
  • @Madbreaks - 请注意,BlackLab.equals 被覆盖,因此它默认为 Dog.equals,即 super.equals,因此是 Dog.equals,因为 BlackLab.superDog.
  • @OldCurmudgeon 在Dog 中对super.equals 的调用将始终解析为Animal.equals(它不会根据运行时类型而改变)。
  • 呃,有谁知道
  • @CorayThan 使用带有`字符的内联代码格式,例如`Dog`格式为Dog&lt;Owner&gt;。你也可以使用 <和>对于 .

标签: java equals


【解决方案1】:

编辑后编辑:

简而言之,是的,根本不要打电话给super.equals。另外,确保super.equals 使用类相等而不是instanceof 检查其传入对象的类。这就是Object.equals 默认所做的,所以如果Animal 没有声明equals,你很好。

不过,我的其余答案仍然相关,因为上面基本上是对上一段中结论的总结,就像以前一样。基本上,如果超类不需要等于子类的实例,那么每个人都可以检查对象是否相等(不是instanceof)以及他们想要的任何状态。如果超类只检查instanceof(不是更严格的相等性),那么基本上子类不能检查除了超类检查的任何额外状态。

在您的情况下,Animal 不会检查任何内容,而 Dog 会检查。所以你很高兴。

原答案如下:


如果 (a) 它们在某些特定的 BlackLab 品质上相等并且 (b) 它们在 Dogness 上相等,您是否认为两个 BlackLab 是相等的?如果是这样,这是一个坏主意!它破坏了Object.equals 指定的相等的传递属性。想象一下你有:

  • BlackLab 一个
  • BlackLab b
  • 狗c

假设这三个人的“dogness”是相等的,例如他们的体重、身高和名字。但是 BlackLabness 的两个 BlackLab 并不相同,比如他们的外套的光泽。现在你有:

  • a = c
  • b = c
  • a != b // 破坏传递性!

另一方面,如果BlackLab.equals 没有添加任何额外的检查——那么您根本不需要覆盖它。它只会继承 Dog 的相等性,这正是您想要的。

正如 Mattias Buelens 指出的那样,您可以通过更严格的类型检查来解决这个问题——要求两个对象属于同一个确切的类,而不是使用 instanceof。这修正了平等合同,但代价是体重 80 磅的 BlackLab“雷克斯”不等于体重 80 磅、名为“雷克斯”的未指定品种的狗。如果您考虑一下您需要的特定用例,您可能会发现这是完全可以接受的。

【讨论】:

  • 请注意,您可以通过添加 this.getClass() == obj.getClass() 检查来保留传递性。
  • 嗯。也许我的例子有点糟糕。让我尝试修复它......我修复它是为了解释这样做的原因与 Dog 类中具有泛型的事实有关。
  • 我确实在 Animal 中有一些相关的东西。我删除了对 super.equals 的调用,并使用 getter 添加了这些检查。所以我现在让它工作了,虽然我真的不太喜欢它。如果我改变 Animal 及其 equals 方法的行为,我不应该在它的后代类中修改相同的东西,这就是继承的意义!他们确实已经使用了类相等,而不是 instanceof。 (虽然我最近也了解到,对于 Hibernate 实体,我必须使用 instanceof ... grrr)
  • 我想我可以在 Animal 中创建一个受保护的辅助方法,例如 animalFieldsAreEqual,该方法调用动物的 equal 方法,而 Dog 在它的 equals 中也是如此......不过我也不喜欢那么多。
  • 你可以做一个混合,其中equals 负责样板并调用animalFieldsAreEqual,它可以以通常的超级方式使用继承。像这样:gist.github.com/yshavit/212ea023e549e833d250
【解决方案2】:

你误解了supersuper.x() 始终是对 calling 类的超类的调用。

在你的例子中调用super.equals()的类是Dog而不是BlackLab,所以它调用Dog的超类(因此Animal.equals())而不是BlackLab的超类(这将是Dog)。

可以这样想:如果您不覆盖 equals(或任何其他方法),它会隐式定义为:

class BlackLab extends Dog {
  ...
  boolean equals( Object o ) {
    return super.equals( o );
  }
  ...
}

【讨论】:

  • 如果这是真的,那我不知道为什么会这样。当我使用 eclipse 调试代码时,我可以看到它从super.equals(obj) 跳转到它已经在其中的相同方法。我确实尝试在 BlackLab 中实现一个只返回 super.equals 的 .equals,但它仍然做了同样的事情。
  • 接受这个答案,因为它基本上解释了我描述的情况不可能是真的,并且我错误地描述了 super.x 的实际行为
猜你喜欢
  • 2023-04-10
  • 2023-03-29
  • 2016-11-10
  • 2014-09-10
相关资源
最近更新 更多