在子类中覆盖equals()和hashCode()……考虑到超级字段


68

关于如何考虑超字段的子类中的Override equals()hashCode()in,是否有特定规则?知道有很多参数:超字段是private / public,有/没有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;
    }
}

我不认为智人扩展了人科。至少,那里也必须是HomoErectus。
Pavel_K

Answers:


63

儿童不应该检查父母的私人成员

但是显然,对于相等性和哈希,应考虑所有重要字段。

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

假设您没有被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怎么了,没有通过调用超类方法将超类字段考虑在内?


@CPerkins:很难自动实现这一代,考虑带有/不带有getter的私有字段的情况……我认为这就是为什么IDE不这样做的原因……
wj。

@wj,您所要做的就是调用超类equals和hashcode方法,并将这些字段考虑在内。IDE可以轻松地做到这一点。孩子不必显式检查父母的私人成员。
CPerkins,2010年

53
我认为“孩子不应该检查父母的私人成员”是非常合理的父母建议。
OliBlogger 2012年

20
equals方法不遵循均等合约。它不是对称的,因为subclassInstance.equals(parentInstance)由于getClass()检查永远无法返回true,但是在使用中隐含了可以返回truesuper.equals的假设parentInstance.equals(subclassInstance)
MikeFHay 2014年

2
如果super#equals工作原理相同,则super.equals(obj)始终返回false
晋权

22

我更喜欢使用commons-lang包中的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();
}

1
指称.appendSuper()
FelipeLeão17年

8

一般来说,在子类之间实现均等很难保持对称和可传递。

考虑一个超类,对现场检查xy,和子类的检查xyz

因此,子类==父类==子类,其中z在子类的第一个实例与第二个实例之间是不同的,这违反了合同的可传递部分。

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

因此,结果是子类当然可以考虑super.equals(),但还应该进行自己的getClass()检查以避免上述问题,然后另外在其自己的字段上检查是否等于。这将是一类奇怪的鸭子,它会根据超类的特定字段而不是仅仅在超类返回equals的情况下更改其自己的equals行为。


thx Yishai,但是这里的问题是比较两个子类的实例,问题是我们不能做类似“ super.equals(obj.super)”的事情,其中​​obj是比较对象
wj。

@wj,只要你的等级和obj的类都是一样的,我不知道为什么你不能说'如果返回错误”(super.equals(OBJ)!)
伊沙伊

@Yishai,这里的要点是找到一种比较“ this.super”和“ obj.super”而不是“ this.super”和“ obj”的方法,因为它们不是同一类的直接实例,意味着“ super.equals( obj)”始终为假...在我的示例中,“ obj。”是“ HomoSapiens”时,“ this.super”是“人科”
wj。

@wj,我认为您不了解我。我在@matt b的答案中建议与Equals构建器中的appendSuper方法相同。
Yishai 2010年

6

规则是:

  • 这是自反的:对于任何非空参考值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,只要未修改对象的equals比较中使用的信息,对x.equals(y)的多次调用将始终返回true或始终返回false。
  • 对于任何非null参考值x,x.equals(null)应该返回false。
  • 通常有必要在重写此方法时重写hashCode方法,以便维护hashCode方法的常规协定,该协定规定相等的对象必须具有相等的哈希码

来自Object.equals()

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


最好看一下规范,而不是(或除了)实现和经验。
乔纳森·罗森

请注意,产生相同结果的两个对象hashCode不一定equals是彼此。
晋权

3

好吧,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());
}

2

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


同样,覆盖equals()hashCode()Hominidae会在相应的方法HomoSapiens比较容易实现。
2010年

@trashgod:在人科中重写equals()和hashCode()不会使HomoSapiens中的相应方法更容易实现,因为我们无法做类似“ super.equals(obj.super)”的事情,其中​​obj是被比较的对象...
wj。

@wj:你是对的。我在想这个例子中,它调用的相应方法Stringstackoverflow.com/questions/1924728/...
trashgod

@trashgod:实际上我是完全错误的……而你是绝对正确的:)
wj。

2

关于公认的@CPerkins答案,由于super.equals()方法也将检查类的相等性,我认为给定的equals()代码不能可靠地工作。子类和超类将没有相等的类。


这不是答案,而是评论。这是一个非常有效的评论。
Adriaan Koster

超类的super.equals()和super.hashcode()应该以它们对子类有效的方式编写,如果不是,则应为最终形式。
乔纳森·罗森

反应有点晚,但仍然:这一点都没有问题。毕竟:调用super.equals的那一刻,您仍然从子类的实例中调用它。包含(this。)getClass()的超类不会更改在实例上调用方法的事实,并且该实例的类也不会更改。
Stultuske '17

1

听起来您的父(超级)类没有覆盖等值。如果是这种情况,则在子类中重写此方法时,需要比较父类中的字段。我同意使用普通的EqualsBuiler是可行的方法,但是您需要注意不要破坏等价合约的对称/过渡部分。

如果您的子类向父类添加了属性,而父类不是抽象的,并且覆盖了等号,那么您将陷入麻烦。在这种情况下,您应该真正查看对象组成而不是继承。

我强烈建议Joshua Block撰写的有效Java中的这一节。它很全面,而且解释得很好。


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;
}

仅当您不首先自动生成super时,才会发生问题。请在Netbeans下检查以上内容。


1
不。查看equals()实现的第二行。如果超类具有相同的行(应该如此),则对super.equals(o)的调用将失败,因为它们的类型不同。子类确实需要比较定义相等性的所有属性,而不仅仅是局部声明的属性。从语义上讲,即使通过getter也无法访问的私有字段实际上不应作为子类中的相等检查的一部分。为什么要关心看不见的差异?
ideaculptor

super.equals可能以Object#equals结尾,即身份...请注意
Max

0

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

EqualsBuilder.reflectionEquals(this,o);

HashCodeBuilder.reflectionHashCode(this);


1
进行反射非常昂贵,并且必须以优化的性能方式实现这两种方法。
最多

显然,显而易见是显而易见的。但是,严重的是,我不认为这些方法所进行的反射在性能上比编写代码要昂贵得多。
Terry H

1
stackoverflow.com/questions/435553/java-reflection-performance 1.例如,如果您具有一个hashSet,并且想要添加一个项目,则hashCodeequals可能会被大量使用。2.如果您通过反射实现hashCode和/或equals,则可以考虑该对象的所有实例变量。而且您可能会避免一些(我想到一个可怕的例子:一个包含元数据和文件/流的对象)。
最多

但这似乎都不重要。您可以提出所需的所有可怕场景,但是如果需要equals或hash函数来考虑所有这些情况,则必须将它们考虑在内。您可以使用内置的反射或编写代码来执行此操作,但是必须这样做。我发现很少有人能写出比这些函数内置的性能更高的代码。当然可以做到,但是在实践中很少发生。
Terry H
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.