繁体   English   中英

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

[英]What is the proper way to implement a robust equals() and hashCode() method in an inheritance hierarchy?

我有以下抽象的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)为true

当我在子类内部调用超类的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);

    }

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

将实现放到超类的equals运算符中是否更健壮,还是要使用子类中通过getClass()方法进行更明确的测试以符合合同,这会更好吗?

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

如果我的问题过于笼统,我深表歉意,但我尽量不问得太清楚。

编辑:

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

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

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

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

在此处输入图片说明

在此处输入图片说明

这是我从阅读《有效的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
  • Null:对于非null xx.equals(null) == false

高质量等于方法:

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

最后警告:

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

有效Java 2nd Edition中的Hashcode直接引用

  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。 如果该字段的值为null ,则return 0 (或其他常数,但是0是传统的)。
      7. 如果该字段是数组,则将其视为每个元素都是一个单独的字段。 也就是说,通过递归应用这些规则为每个重要元素计算哈希码,并按照步骤2.b组合这些值。 如果数组字段中的每个元素都很重要,则可以使用版本1.5中添加的Arrays.hashCode方法之一。
    • 将步骤2.a中计算的哈希码c组合为以下result = 31 * result + 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似乎已经可以唯一地标识任何人,所以您应该仅在id使用它进行比较,而不要在任何子类中覆盖它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM