首页 文章

覆盖子类中的equals()和hashCode()...考虑超级字段

提问于
浏览
55

是否有一个特定的规则如何覆盖 equals()hashCode()sub classes 考虑 super fields ??知道有很多参数:超级字段是私有/公共的,有/无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;
    }
}

10 回答

  • 5

    Children should not examine the private members of their parents

    但显然,所有重要的领域都应该考虑到平等和散列 .

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

    假设您没有使用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
            }
    

    当然,哈希码很简单:

    @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没有通过调用超类方法来考虑超类字段?

  • 56

    我更喜欢使用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();
    }
    
  • 2

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

    考虑一个超类检查字段 xy ,子类检查 xyz .

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

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

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

  • 2

    规则是:

    • 它是自反的:对于任何非空引用值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() .

    因此,请使用完成规则所需的字段 .

  • 0

    因为继承会破坏封装,所以实现equals()和hashCode()的子类必然会考虑其超类的特殊性 . 我已成功编码从子类的方法调用父类的equals()和hashCode()方法 .

  • 1

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

  • 0

    听起来你的父(超级)类不会覆盖equals . 如果是这种情况,则在子类中重写此方法时,需要比较父类中的字段 . 我同意使用公共EqualsBuiler是可行的方法,但你需要注意不要破坏等于 Contract 的对称/转换部分 .

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

    我强烈推荐Joshua Block关于Effective Java的部分 . 它是全面的,非常好解释 .

  • 8

    那么, 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());
    }
    
  • 1

    值得注意的是,IDE自动生成可能已经考虑了超类,只要提供了超类的equals()和hashCode() . 也就是说,应首先自动生成super这两个函数,然后自动生成子元素 . 我在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下方查看 .

  • 20

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

    EqualsBuilder.reflectionEquals(this,o);

    HashCodeBuilder.reflectionHashCode(本);

相关问题