在继承层次结构中实现健壮的equals()和hashCode()方法的正确方法是什么?

时间:2018-11-14 00:19:28

标签: java inheritance equals hashcode

我有以下抽象Person类:

import java.util.Objects;

public abstract class Person {

    protected String name;
    protected int id;

    public Person(String name, int id) {
        this.name = name;
        this.id = id;
    }

    public abstract String description();

    @Override
    public boolean equals(Object obj) {
        if(this == obj) return true;
        if(!(obj instanceof Person)) return false;

        return Objects.equals(this.name, ((Person) obj).name) &&
                this.id == ((Person) obj).id;
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.name, this.id);
    }
}

现在我有一个Person的子类,称为Employee

import java.time.LocalDate;
import java.util.Objects;

public class Employee extends Person {

    private double salary;
    private LocalDate hireDay;

    public Employee(String name, int id, double salary, int year, int month, int day) {
        super(name, id);
        this.salary = salary;
        this.hireDay = LocalDate.of(year, month, day);
    }

    @Override
    public String description() {
        return "Employee with a salary of " + this.salary;
    }

    @Override
    public int hashCode() {
        return super.hashCode() + Objects.hash(this.salary,this.hireDay);
    }

    @Override
    public boolean equals(Object obj) {

        return super.equals(obj) && 
        Double.compare(this.salary, ((Employee) obj).salary) == 0
              && Objects.equals(this.hireDay,((Employee)obj).hireDay);

}

要正确实现equals方法,它必须符合以下约定。

  

反身:x.equals(x)始终为True
    对称:x.equals(y)等效于y.equals(x)
    传递式:x.equals(y)和y.equals(z)表示x.equals(z)是真实的

当我在子类内部调用超类的equals()方法时,我首先确保要比较的所有对象都是超类的子类。此问题解决了比较混合类型的问题,并照顾了上述合同。我不再需要使用以下equals实现:

    @Override
    public boolean equals(Object obj) {

        if(this == obj) return true;
        else if(obj == null || this.getClass() != obj.getClass()) return false;

        Employee other = (Employee) obj;

        return Objects.equals(this.name, other.name) &&
               Double.compare(this.salary, other.salary) == 0 &&
               Objects.equals(this.hireDay, other.hireDay);

    }

即,由于使用this运算符的超类中的方法,我不再需要明确检查当前对象(obj)是否与instance of属于同一类。 。

将实现放到超类的equals运算符中是否更健壮,还是更好的方法是使用getClass()方法在子类中使用更明确的测试以符合合同规定? >

就hashCode()方法而言,我对专用于子类的私有实例字段进行哈希处理,然后将其简单地添加到超类中hash方法的结果中。我找不到任何文档来说明这是否是在一般层次结构或继承层次结构中实现hashCode()函数的正确方法。我看到了人们明确指定自己的哈希函数的代码。

对于我的问题过于笼统,我深表歉意,但我会尽力向他们提问,不要太含糊。

编辑:

我要求Intellij实现一个equals和hashcode方法,它决定采用我上面发布的最后一个实现。那么,在什么情况下我将在超类中使用instance of?会在我在超类中实现最终的equals方法时(例如仅根据用户ID比较Person对象)吗?

3 个答案:

答案 0 :(得分:1)

两个人可能有相同的id吗?不应该这样因此该逻辑扩展到Employee类,这意味着在equals类中实现hashCodePerson就足够了。

在这一点上,由于您只处理int,因此可以对Integer.hashCode(id)使用hashCode,而只需比较equals的值。 / p>

答案 1 :(得分:1)

如果要实现equals和hashcode方法,请使用eclipse,只需在文件中单击鼠标右键,然后转到source并选择带有所需字段的generate equals()&hashcode(),如下所示:

enter image description here

enter image description here

答案 2 :(得分:1)

这是我阅读《有效Java第二版》时的笔记:

等于 必须遵守总合同:

  • 反身:针对non-null xx.equals(x) == true
  • 对称:对于non-null x,yx.equals(y) <==> y.equals(x)
  • 传递性:对于non-null x,y,zx.equals(y) and y.equals(z) ==> x.equals(z) == true
  • 一致:对于任何非空x,y:如果x.equals(y) == true,那么如果xy中没有变化,则所有调用都必须返回true。
  • 空:表示非空xx.equals(null) == false

高质量等于方法:

  1. 使用==检查参数是否是对此对象(x == x)的引用
  2. 使用instanceof检查参数是否为正确的类型(还检查null
  3. 将参数转换为正确的类型
  4. 对于类中的每个“重要”字段,请检查参数的该字段是否与该对象的相应字段相匹配
  5. 完成后,检查对称,可传递和一致

最后警告:

  • 在覆盖等于时始终覆盖hashCode
  • 不要太聪明
  • 在equals声明中不要用其他类型的Object代替->不值得的是,由于增加了复杂性而导致的性能提升
有效Java 2nd Edition中的

哈希码直接报价

  1. 将一个恒定的非零值(例如17)存储在一个名为result的int变量中。
  2. 对于对象中的每个重要字段f(每个字段都由 equals方法,即执行以下操作:

    • 为该字段计算一个int哈希码c:
      1. 如果该字段是布尔值,请计算(f ? 1 : 0)
      2. 如果该字段是byte, char, short, or int, compute (int) f.
      3. 如果该字段是long, compute (int) (f ^ (f >>> 32)).
      4. 如果该字段是float, compute Float.floatToIntBits(f).
      5. 如果该字段是double, compute Double.doubleToLongBits(f),并且 然后对所得的long进行哈希处理。
      6. 如果该字段是对象引用,并且此类的equals方法 通过递归调用equals来递归比较字段 在字段上调用hashCode。如果比较复杂 必需,为此字段计算“规范表示”,然后 在规范表示形式上调用hashCode。如果值 字段为nullreturn 0(或其他常数,但0为传统值)。
      7. 如果该字段是数组,则将其视为每个元素都是一个单独的字段。 也就是说,通过应用 这些规则是递归的,并按照步骤2.b组合这些值。如果每个 数组字段中的元素很重要,您可以使用 在1.5版中添加了Arrays.hashCode方法。
    • 将步骤2.a中计算的哈希码c合并为以下结果: result = 31 * result + c;
  3. 返回结果。

  4. 完成hashCode方法的编写后,请问自己是否 相等的实例具有相等的哈希码。编写单元测试以验证您的直觉!

因此请遵循以下规则:

@Override
public boolean equals(Object obj) {

    if (this == obj) {
        return true;
    }

    if (!(obj instanceof Employee)) {
        return false;
    }
    Employee other = (Employee) obj;

    return super.equals(other) &&
           Double.compare(this.salary, other.salary) == 0 &&
           this.hireDay.equals(other.hireDay);

}

在您看来,id应该已经可以唯一地标识任何人,因此您应该将其用于等值比较,而不要在任何子类中覆盖它。