【问题标题】:Overriding equals() & hashCode() in sub classes ... considering super fields在子类中覆盖 equals() 和 hashCode() ... 考虑超级字段
【发布时间】:2011-01-05 05:34:45
【问题描述】:

考虑到超级字段,是否有关于如何在子类中覆盖equals()hashCode()的具体规则?知道有很多参数:超级字段是私有/公共的,有/没有getter ...

例如,Netbeans 生成的 equals() 和 hashCode() 不会考虑超级字段...和

    new HomoSapiens("M", "80", "1.80", "Cammeron", "VeryHot").equals(
    new HomoSapiens("F", "50", "1.50", "Cammeron", "VeryHot"))

将返回 true :(

public class Hominidae {

    public String  gender;
    public String  weight;
    public String  height;

    public Hominidae(String gender, String weight, String height) {
        this.gender = gender;
        this.weight = weight;
        this.height = height;
    }
    ... 
}

public class HomoSapiens extends Hominidae {
    public String name;
    public String faceBookNickname;

    public HomoSapiens(String gender, String weight, String height, 
                       String name, String facebookId) {
        super(gender, weight, height);
        this.name = name;
        this.faceBookNickname = facebookId;
    }
    ...  
}

如果你想查看 Netbeans 生成的 equals() & hashCode() :

public class Hominidae {

    ...

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Hominidae other = (Hominidae) obj;
        if ((this.gender == null) ? (other.gender != null) : !this.gender.equals(other.gender)) {
            return false;
        }
        if ((this.weight == null) ? (other.weight != null) : !this.weight.equals(other.weight)) {
            return false;
        }
        if ((this.height == null) ? (other.height != null) : !this.height.equals(other.height)) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 37 * hash + (this.gender != null ? this.gender.hashCode() : 0);
        hash = 37 * hash + (this.weight != null ? this.weight.hashCode() : 0);
        hash = 37 * hash + (this.height != null ? this.height.hashCode() : 0);
        return hash;
    }

}


public class HomoSapiens extends Hominidae {

    ...

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final HomoSapiens other = (HomoSapiens) obj;
        if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
            return false;
        }
        if ((this.faceBookNickname == null) ? (other.faceBookNickname != null) : !this.faceBookNickname.equals(other.faceBookNickname)) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 89 * hash + (this.name != null ? this.name.hashCode() : 0);
        hash = 89 * hash + (this.faceBookNickname != null ? this.faceBookNickname.hashCode() : 0);
        return hash;
    }
}

【问题讨论】:

  • 我不认为智人扩展了人科。至少,那里也必须有直立人。

标签: java inheritance overriding equals hashcode


【解决方案1】:

孩子不应该检查父母的私人成员

但是显然,所有重要的字段都应该考虑到相等性和散列。

幸运的是,您可以轻松满足这两个规则。

假设您没有卡住使用 NetBeans 生成的 equals 和 hashcode,您可以修改 Hominidae 的 equals 方法以使用 instanceof 比较而不是类相等,然后直接使用它。像这样的:


    @Override  
    public boolean equals(Object obj) {  
        if (obj == null) { return false; }  
        if (getClass() != obj.getClass()) { return false; }  
        if (! super.equals(obj)) return false;
        else {
           // compare subclass fields
        }

当然,hashcode 很简单:


    @Override     
    public int hashCode() {     
        int hash = super.hashCode();
        hash = 89 * hash + (this.name != null ? this.name.hashCode() : 0);     
        hash = 89 * hash + (this.faceBookNickname != null ? this.faceBookNickname.hashCode() : 0);     
        return hash;     
    }     

说真的:NetBeans 没有通过调用超类方法来考虑超类字段,这是怎么回事?

【讨论】:

  • 我认为“孩子不应该检查父母的私人成员”是非常合理的父母建议。
  • equals 方法不遵循equals contract。它不是对称的,因为 subclassInstance.equals(parentInstance) 由于 getClass() 检查而永远无法返回 true,但在您使用 super.equals 时隐含的是假设 parentInstance.equals(subclassInstance) 可以返回 true。
  • 如果super#equals 工作相同,super.equals(obj) 总是返回false
  • 我为 NetBeans issues.apache.org/jira/browse/NETBEANS-4512开了一个问题
  • @MikeFHay super.equals 仅对同一类的实例调用,this.getClass() 仍将返回子类,即使它在父类的 equals 方法内。这里没有破坏对称性。
【解决方案2】:

我更喜欢使用commons-lang package 中的EqualsBuilder(和HashcodeBuilder)来使我的equals() 和hashcode() 方法更易于阅读。

例子:

public boolean equals(Object obj) {
 if (obj == null) { return false; }
 if (obj == this) { return true; }
 if (obj.getClass() != getClass()) {
   return false;
 }
 MyClass rhs = (MyClass) obj;
 return new EqualsBuilder()
             .appendSuper(super.equals(obj))
             .append(field1, rhs.field1)
             .append(field2, rhs.field2)
             .append(field3, rhs.field3)
             .isEquals();
}

【讨论】:

  • 感谢.appendSuper()
【解决方案3】:

一般来说,跨子类实现 equals 很难保持对称和传递。

考虑一个检查字段xy 的超类,以及检查xyz 的子类。

所以一个 Subclass== Superclass== Subclass 其中 z 在 Subclass 的第一个实例和第二个实例之间是不同的,违反了合同的传递部分。

这就是为什么 equals 的典型实现会检查 getClass() != obj.getClass() 而不是实例化的原因。在上面的示例中,如果 SubClass 或 Superclass 进行 instanceof 检查,则会破坏对称性。

所以结果是子类当然可以考虑 super.equals() 但也应该进行自己的 getClass() 检查以避免上述问题,然后另外检查自己的字段是否相等。这将是一个奇怪的类鸭子,它根据超类的特定字段改变自己的 equals 行为,而不仅仅是超类返回 equals。

【讨论】:

  • thx Yishai,但这里的问题是比较子类的 2 个实例,问题是我们不能做像“super.equals(obj.super)”这样的事情,其中​​ obj 是比较对象
  • @wj,只要你的类和obj类是一样的,我不明白你为什么不能调用`if (!super.equals(obj)) return false'
  • @Yishai,这里的重点是找到一种方法来比较“this.super”和“obj.super”而不是“this.super”和“obj”,因为它们不是相同的直接实例类意味着“super.equals(obj)”总是错误的......在我的例子中,“this.super”是“Hominidae”,而“obj”是“HomoSapiens”
  • @wj,我不认为你理解我。我建议与 @matt b 的答案中 Equals 构建器中的 appendSuper 方法相同。
【解决方案4】:

规则是:

  • 它是自反的:对于任何非空引用值 x,x.equals(x) 应该返回 true。
  • 它是对称的:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应该返回 true。
  • 它是可传递的:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true 并且 y.equals(z) 返回 true,则 x.equals(z) 应该返回 true .
  • 这是一致的:对于任何非空引用值 x 和 y,x.equals(y) 的多次调用始终返回 true 或始终返回 false,前提是没有修改对象上 equals 比较中使用的信息。李>
  • 对于任何非空引用值 x,x.equals(null) 应返回 false。
  • 通常需要在任何时候重写 hashCode 方法,以维护 hashCode 方法的一般约定,即相等的对象必须具有相等的哈希码

来自Object.equals()

因此,请使用满足规则所需的字段。

【讨论】:

  • 查看规范而不是(或除此之外)实现和体验总是好的。
  • 请注意,产生相同hashCode 的两个对象不一定是equals
【解决方案5】:

好吧,HomoSapiens#hashcode 对 CPerkins 的回答就足够了。

@Override     
public int hashCode() {     
    int hash = super.hashCode();
    hash = 89 * hash + Objects.hash(name);     
    hash = 89 * hash + Objects.hash(faceBookNickname);     
    return hash;     
}

如果您希望这些父字段(genderweightheight)起作用,一种方法是创建父类型的实际实例并使用它。感谢上帝,它不是一个抽象类。

@Override
public boolean equals(Object obj) {
    if (obj == null) {
        return false;
    }
    if (getClass() != obj.getClass()) {
        return false;
    }
    final HomoSapiens other = (HomoSapiens) obj;
    if (!super.equals(new Hominidae(
        other.gender, other.weight, other.height))) {
         return false;
    }
    if (!Objects.equals(name, other.name)) return false;
    if (!Objects.equals(faceBookNickname, other.faceBookNickname))
        return false;
    return true;
}

我正在添加一种方法来(我认为)解决这个问题。关键是添加一个松散地检查相等性的方法。

public class Parent {

    public Parent(final String name) {
        super(); this.name = name;
    }

    @Override
    public int hashCode() {
        return hash = 53 * 7 + Objects.hashCode(name);
    }

    @Override
    public boolean equals(final Object obj) {
        return equalsAs(obj) && getClass() == obj.getClass();
    }

    protected boolean equalsAs(final Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (!getClass().isAssignableFrom(obj.getClass())) return false;
        final Parent other = (Parent) obj;
        if (!Objects.equals(name, other.name)) return false;
        return true;
    }

    private final String name;
}

Child 来了。

public class Child extends Parent {

    public Child(final String name, final int age) {
        super(name); this.age = age;
    }

    @Override
    public int hashCode() {
        return hash = 31 * super.hashCode() + age;
    }

    @Override
    public boolean equals(final Object obj) {
        return super.equals(obj);
    }

    @Override
    protected boolean equalsAs(final Object obj) {
        if (!super.equalsAs(obj)) return false;
        if (!getClass().isAssignableFrom(obj.getClass())) return false;
        final Child other = (Child) obj;
        if (age != other.age) return false;
        return true;
    }

    private final int age;
}

测试...

@Test(invocationCount = 128)
public void assertReflective() {
    final String name = current().nextBoolean() ? "null" : null;
    final int age = current().nextInt();
    final Child x = new Child(name, age);
    assertTrue(x.equals(x));
    assertEquals(x.hashCode(), x.hashCode());
}

@Test(invocationCount = 128)
public void assertSymmetric() {
    final String name = current().nextBoolean() ? "null" : null;
    final int age = current().nextInt();
    final Child x = new Child(name, age);
    final Child y = new Child(name, age);
    assertTrue(x.equals(y));
    assertEquals(x.hashCode(), y.hashCode());
    assertTrue(y.equals(x));
    assertEquals(y.hashCode(), x.hashCode());
}

@Test(invocationCount = 128)
public void assertTransitive() {
    final String name = current().nextBoolean() ? "null" : null;
    final int age = current().nextInt();
    final Child x = new Child(name, age);
    final Child y = new Child(name, age);
    final Child z = new Child(name, age);
    assertTrue(x.equals(y));
    assertEquals(x.hashCode(), y.hashCode());
    assertTrue(y.equals(z));
    assertEquals(y.hashCode(), z.hashCode());
    assertTrue(x.equals(z));
    assertEquals(x.hashCode(), z.hashCode());
}

【讨论】:

    【解决方案6】:

    因为继承破坏了封装,实现 equals() 和 hashCode() 的子类必须,必然,说明其超类的特性。我已经成功地从子类的方法对父类的 equals() 和 hashCode() 方法进行编码调用。

    【讨论】:

    • 同样,在Hominidae 中覆盖equals()hashCode() 将使HomoSapiens 中的相应方法相对更容易实现。
    • @trashgod: 在 Hominidae 中覆盖 equals() 和 hashCode() 不会使 HomoSapiens 中的相应方法更容易实现,因为我们不能做类似“super.equals(obj.super)”这样的事情,其中obj 是比较对象...
    • @wj:你是对的。我在想这个例子,它调用了String的对应方法:stackoverflow.com/questions/1924728/…
    • @trashgod:事实上我完全错了......你完全正确:)
    【解决方案7】:

    关于接受的@CPerkins 答案,我认为给定的 equals() 代码不会可靠地工作,因为 super.equals() 方法也可能会检查类是否相等。子类和超类不会有相同的类。

    【讨论】:

    • 这不是答案,而是评论。这是一个非常有效的评论。
    • 超类的 super.equals() 和 super.hashcode() 应该以这样的方式编写,它们确实适用于子类,如果不是,它们应该是最终的。
    • 反应有点晚,但仍然:这完全没有问题。毕竟:调用 super.equals 的那一刻,你仍然从子类的实例中调用它。包含 (this.)getClass() 的超类不会改变在实例上调用方法的事实,并且该实例的类也不会改变。
    【解决方案8】:

    听起来您的父(超级)类没有覆盖等于。如果是这种情况,那么当您在子类中覆盖此方法时,您需要比较父类中的字段。我同意使用公共 EqualsBuiler 是可行的方法,但您确实需要小心不要破坏 equals 合约的对称/可交易部分。

    如果您的子类向父类添加属性并且父类不是抽象的并且覆盖等于,那么您将遇到麻烦。在这种情况下,您应该真正关注对象组合而不是继承。

    我强烈推荐 Joshua Block 在 Effective Java 中关于此的部分。它很全面,而且解释得很好。

    【讨论】:

      【解决方案9】:

      值得注意的是,IDE自动生成可能已经考虑到超类,只要超类的equals()和hashCode()存在。也就是应该先自动生成super的这两个函数,然后再自动生成child。我在 Intellj Idea 下得到了正确的例子:

      @Override
      public boolean equals(Object o) {
          if (this == o) return true;
          if (o == null || getClass() != o.getClass()) return false;
          if (!super.equals(o)) return false;
      
          TActivityWrapper that = (TActivityWrapper) o;
      
          return data != null ? data.equals(that.data) : that.data == null;
      }
      
      @Override
      public int hashCode() {
          int result = super.hashCode();
          result = 31 * result + (data != null ? data.hashCode() : 0);
          return result;
      }
      

      问题发生在您不首先自动生成超级时。请在上面的 Netbeans 下查看。

      【讨论】:

      • 不。查看您的 equals() 实现的第二行。如果超类具有相同的行(应该如此),那么对 super.equals(o) 的调用将失败,因为它们的类型不同。子类确实需要比较所有定义相等的属性,而不仅仅是本地声明的属性。从语义上讲,即使通过 getter 也无法访问的私有字段实际上不应该成为子类中相等检查的一部分。为什么要关心看不见的差异?
      • super.equals 可能会以 Object#equals 结尾,这是身份......请注意
      【解决方案10】:

      我相信他们现在有一种方法可以为您做到这一点:

      EqualsBuilder.reflectionEquals(this, o);

      HashCodeBuilder.reflectionHashCode(this);

      【讨论】:

      • 进行反射非常昂贵,这两种方法必须以优化的性能方式实现。
      • 显然,显而易见。但说真的,我不认为这些方法完成的反射在性能上会比编写代码来实现它的成本更高。
      • stackoverflow.com/questions/435553/java-reflection-performance 1. 例如,如果你有一个 hashSet,并且你想添加一个项目,那么 hashCodeequals 可能/被密集使用。 2. 如果您使用反射实现您的 hashCode 和/或 equals,那么您可以考虑所有对象的实例变量。你可能会避免一些(我有一个可怕的例子:一个包含元数据和文件/流的对象)。
      • 不过,这些似乎都不重要。你可以想出你想要的所有可怕的场景,但是如果你需要你的 equals 或 hash 函数来考虑所有这些事情,你必须考虑到它们。您可以使用内置的反射或编写代码来执行此操作,但必须这样做。我发现很少有人可以编写比这些函数内置的代码更高效的代码。当然可以,但在实践中很少发生。
      猜你喜欢
      • 2010-09-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多