比较 类 扩展相同的超类

Compare classes extending the same superclass

假设我们有一个 class MyEntity,它有一些带有 getter 和 setter 的字段。此外 classes EntityAEntityB 扩展 MyEntityMyEntityA 中有一些字段不在 MyEntityB 中,反之亦然。当我们谈论实体时,EntityAEntityB 确实有自己的 equals-方法,看起来像

@Override
public boolean equals(Object other) {
    if ((this == other))
        return true;
    if ((other == null))
        return false;
    if (!(other instanceof EntityA))
        return false;
    EntityA castOther = (EntityA) other;

    return ((this.getId() == castOther.getId()) || (this.getId() != null && castOther.getId() != null && this.getId().equals(castOther.getId())));
}

休眠需要此 equals 方法来识别唯一实体。

现在我想将 EntityA 的实例与 EntityB 的实例进行比较。如果 MyEntity 中的所有字段都匹配,我将它们定义为相同。

为了检查这一点,我让 eclipse 为我生成 equals 方法并将其复制到 isEqualWithRegardsToContent(MyEntity other).

之类的方法中

我发现这种方法存在一个大问题: 如果有人向其中一个实体添加了一个新列并且没有更新 isEqualWithRegardsToContent(MyEntity other) 方法,它就会出现错误:实体可能被认为在内容方面是平等的,尽管它们不是。

我认为单元测试在这里没有帮助。

您有什么最佳做法吗?

一般来说,在处理继承树时不可能有一个功能齐全的 equals。 superclass.equals(subclass) 不会 return 与 subclass.equals(superclass) 相同的结果,打破对称性(这是平等的基本契约 - 如果 a.eq(b) 则也 b.eq(a))。

您可以仅在顶层实现 equals(),从而从超类的角度比较整个层次结构。这会起作用,但不会与 "all fields" (仅超类中的那些)进行比较。通常实体可以仅通过它们的主键进行比较

就我个人而言,我通常不会混合 2 - equals()hashcode() 我保留用于非多态的简单数据存储 类(或用于在地图中查找的键)。

在此处查看详细报道 - http://www.angelikalanger.com/Articles/JavaSolutions/SecretsOfEquals/Equals-2.html

A class' equals() 方法应该只适用于 class 的其他实例。如果您想将 EntityAEntityBequals() 进行比较,那么您的 equals() 方法应该定义在 MyEntity 上(连同 hashCode())并且EntityAEntityB 本身都不应该有 equals() 方法。否则,你很有可能运行与contract for equals()发生冲突。 Hibernate 等库依赖于 equals() 正确遵守合同。

如果我正确理解你的问题:

  • 您有一个名为 MyEntity 的 class 缺少 getId 字段
  • 你有 2 个子classes EntityAEntityB 都定义了一个 id 字段但是形成了不同的生成器
  • 当比较 class EntityA(resp. EntityB)的 2 个对象时,当且仅当它们的 id 字段具有相同的值时,它们比较相等 - 我假设在那种情况下所有从 MyEntity 继承的字段具有相同的值
  • EntityA 对象与 EntityB 对象进行比较时,当且仅当从 MyEntity 继承的所有字段都相等时,它们应该比较相等。

您可以执行以下操作:

  • MyEntity 中声明一个 equals 方法,当且仅当所有字段比较相等时 returns 为真 - 如果您不能为此使用 equals 方法,名称方法ABequals(例如)
  • 在 subclass 中声明 equal 方法 EntityA (resp. EntityB)

    @Override
    public boolean equals(Object other) {
        if ((this == other))
            return true;
        if ((other == null))
            return false;
        if (!(other instanceof EntityA))
            return MyEntity.equals(other);  // or return ABequals(other);
        EntityA castOther = (EntityA) other;
    
        return ((this.getId() == castOther.getId()) || (this.getId() != null && castOther.getId() != null && this.getId().equals(castOther.getId())));
    }
    

在那种情况下,您必须MyEntityclass中实现一个使用所有字段的散列方法,您不应该在子classes。这样你就可以确保比较相等的两个对象将具有相同的哈希值 - 上面的定义已经保证 equals 函数是一个等价关系。

比如说,你在 superclass 中有 equals() 方法比较公共属性。

在subclass equals()中可以先调用super.equals(),然后,如果比较的对象也是这个class,只比较特定的属性。所以在 EntityA 你写:

@Override
public boolean equals(Object o) {
    boolean eq = super.equals(o);
    if (eq && o instanceof EntityA) {
        EntityA e = (EntityA) o;
        return Objects.equals(this.propOne, e.propOne) 
            && Objects.equals(this.propTwo, e.propTwo)
            && // compare other properties
    } else 
       return eq;
}

这样,相同具体 class 的对象将通过全套属性进行比较,包括通用属性和特定属性,而不同 classes 的实例将仅通过通用属性进行比较。虽然这是违反合同传递属性的非标准方式,但它可能会解决您的特定问题。