关于java:覆盖equals()&

Overriding equals() & hashCode() in sub classes … considering super fields

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

例如,netbeans generated equals()&;hashcode()不会考虑超级字段…和

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

将返回true:。(

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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;
    }
}

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

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

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

假设您不使用netbeans生成的equals和hashcode,您可以修改hominidae的equals方法,使用instanceof comparison而不是类equality,然后直接使用它。像这样:

1
2
3
4
5
6
7
8
    @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很简单:

1
2
3
4
5
6
7
    @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没有通过调用超类方法来考虑超类字段,这是怎么回事?


我更喜欢使用commons lang包中的equalsbuilder(和hashcodebuilder)使我的equals()和hashcode()方法更易于阅读。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();
}


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

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

因此,子类==superclass==subclass,其中z在子类的第一个实例和第二个实例之间是不同的,违反了契约的可传递部分。

因此,equals的典型实现将检查getClass() != obj.getClass(),而不是执行instanceof。在上面的例子中,如果子类或超类执行一个检查实例,它将破坏对称性。

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


规则是:

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

来自object.equals()。

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


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


因为继承破坏了封装,所以实现equals()和hashcode()的子类必须解释它们的超类的特性。我成功地对父类的equals()和hashcode()方法的子类方法进行了编码调用。


值得注意的是,如果super类的equals()和hashcode()已经存在,那么IDE自动生成可能已经考虑了super类。也就是说,应该先自动生成这两个超级函数,然后再自动生成子函数。我在intellj的想法下面得到了正确的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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;
}

当你不首先自动生成super's时,问题就发生了。请查看以上Netbeans下的内容。


好吧,埃多克司1〔0〕的回答就足够了。

1
2
3
4
5
6
7
@Override    
public int hashCode() {    
    int hash = super.hashCode();
    hash = 89 * hash + Objects.hash(name);    
    hash = 89 * hash + Objects.hash(faceBookNickname);    
    return hash;    
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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;
}

江户一号〔4〕来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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;
}

测试…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@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());
}

听起来您的父(超级)类不重写equals。如果是这种情况,则在子类中重写此方法时,需要比较父类中的字段。我同意使用Commons Equalsbuiler是一种可行的方法,但是您需要小心,不要破坏Equals合同的对称/交换部分。

如果您的子类向父类添加属性,而父类不是抽象的,并且重写等于,则会遇到麻烦。在这个场景中,您应该真正关注对象组合而不是继承。

我强烈推荐Joshua Block在这个有效的Java方面。它很全面,解释得很好。


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

equalsbuilder.reflectionequals(this,o);

hashcodebuilder.reflectionhashcode(this);