为什么我需要重写Java中的equals和hashCode方法?

2022-08-31 04:24:17

最近,我通读了这个开发人员工作文档

本文档完全是关于定义和有效和正确的,但是我无法弄清楚为什么我们需要覆盖这两种方法。hashCode()equals()

我如何决定有效地实施这些方法?


答案 1

Joshua Bloch谈有效Java

您必须在覆盖 equals() 的每个类中重写 hashCode()。如果不这样做,将导致违反 Object.hashCode() 的一般协定,这将阻止您的类与所有基于哈希的集合(包括 HashMap、HashSet 和 Hashtable)一起正常运行。

让我们尝试通过一个示例来理解它,如果我们在不覆盖的情况下覆盖并尝试使用 .equals()hashCode()Map

假设我们有一个这样的类,如果两个对象相等,则它们的相等(与日食一起并由日食生成)MyClassimportantFieldhashCode()equals()

public class MyClass {
    private final String importantField;
    private final String anotherField;

    public MyClass(final String equalField, final String anotherField) {
        this.importantField = equalField;
        this.anotherField = anotherField;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((importantField == null) ? 0 : importantField.hashCode());
        return result;
    }

    @Override
    public boolean equals(final Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final MyClass other = (MyClass) obj;
        if (importantField == null) {
            if (other.importantField != null)
                return false;
        } else if (!importantField.equals(other.importantField))
            return false;
        return true;
    }
}

想象一下你有这个

MyClass first = new MyClass("a","first");
MyClass second = new MyClass("a","second");

仅覆盖等于

如果仅被覆盖,那么当您首先调用时,将哈希到某个存储桶,当您调用它时,它将哈希到其他某个存储桶(因为它们具有不同的)。因此,尽管它们是相等的,但由于它们不散列到同一个存储桶,因此地图无法实现它,并且它们都保留在地图中。equalsmyMap.put(first,someValue)myMap.put(second,someOtherValue)hashCode


尽管如果我们覆盖,则不必重写,让我们看看在这种特殊情况下会发生什么,我们知道如果两个对象相等,则它们相等,但我们不覆盖。equals()hashCode()MyClassimportantFieldequals()

仅覆盖哈希代码

如果您只覆盖,那么当您调用它时,首先需要,计算它并将其存储在给定的存储桶中。然后,当您调用它时,应根据地图文档将first替换为sec,因为它们是相等的(根据业务需求)。hashCodemyMap.put(first,someValue)hashCodemyMap.put(second,someOtherValue)

但问题是,equals 没有被重新定义,所以当映射散列并循环访问存储桶时,查找是否存在这样的对象,它不会找到任何对象。secondksecond.equals(k)second.equals(first)false

希望它很清楚


答案 2

集合(如 和)使用对象的哈希码值来确定应如何将其存储在集合中,并再次使用哈希代码以在其集合中查找对象。HashMapHashSet

哈希检索过程分为两个步骤:

  1. 找到合适的存储桶(使用hashCode())
  2. 在存储桶中搜索正确的元素 (使用equals() )

这里有一个小例子,说明为什么我们应该过度和.equals()hashcode()

考虑一个具有两个字段的类:年龄和名称。Employee

public class Employee {

    String name;
    int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this)
            return true;
        if (!(obj instanceof Employee))
            return false;
        Employee employee = (Employee) obj;
        return employee.getAge() == this.getAge()
                && employee.getName() == this.getName();
    }

    // commented    
    /*  @Override
        public int hashCode() {
            int result=17;
            result=31*result+age;
            result=31*result+(name!=null ? name.hashCode():0);
            return result;
        }
     */
}

现在创建一个类,将对象插入到 中,并测试该对象是否存在。EmployeeHashSet

public class ClientTest {
    public static void main(String[] args) {
        Employee employee = new Employee("rajeev", 24);
        Employee employee1 = new Employee("rajeev", 25);
        Employee employee2 = new Employee("rajeev", 24);

        HashSet<Employee> employees = new HashSet<Employee>();
        employees.add(employee);
        System.out.println(employees.contains(employee2));
        System.out.println("employee.hashCode():  " + employee.hashCode()
        + "  employee2.hashCode():" + employee2.hashCode());
    }
}

它将打印以下内容:

false
employee.hashCode():  321755204  employee2.hashCode():375890482

现在取消注释方法,执行相同的操作,输出将是:hashcode()

true
employee.hashCode():  -938387308  employee2.hashCode():-938387308

现在你能明白为什么如果两个对象被认为是相等的,它们的哈希码也必须相等吗?否则,您将永远无法找到该对象,因为类 Object 中的默认哈希码方法几乎总是为每个对象提供一个唯一的数字,即使该方法被覆盖,以至于两个或多个对象被视为相等。如果对象的哈希码没有反映这一点,那么它们的相等程度并不重要。所以再一次:如果两个对象相等,它们的哈希码s也必须相等。equals()


推荐