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

最近我读了这个开发工程文件。

该文档全部关于有效且正确地定义hashCode()equals() ,但我无法弄清楚为什么我们需要重写这两个方法。

我怎样才能做出有效实施这些方法的决定?


Joshua Bloch谈到Effective Java

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

让我们尝试用一个例子来理解它,如果我们重写equals()而不重写hashCode()并尝试使用Map ,会发生什么。

假设我们有这样的类,并且MyClass两个对象如果它们的importantField相等(用eclipse生成的hashCode()equals() hashCode() ,它们是相等的)

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

    public String getEqualField() {
        return importantField;
    }

    public String getAnotherField() {
        return 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;
    }

}

重写仅equals

如果只有equals被覆盖,那么当你调用myMap.put(first,someValue)首先散列到某个存储桶中,而当你调用myMap.put(second,someOtherValue)它会散列到其他存储桶(因为它们具有不同的hashCode )。 所以,尽管它们是相同的,但是它们不会散列到同一个桶中,所以地图无法实现,并且它们都留在地图中。


虽然如果我们重写hashCode() ,不需要重写equals() ,但让我们看看在这种特殊情况下会发生什么,我们知道MyClass两个对象如果它们的importantField相等,但我们不覆盖equals()

只覆盖hashCode

想象一下你有这个

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

如果你只重写hashCode那么当你调用myMap.put(first,someValue)时,首先需要计算它的hashCode并将其存储在给定的桶中。 然后,当你调用myMap.put(second,someOtherValue)它应该根据Map文档第一个和第二个替换,因为它们是相等的(根据业务需求)。

但是问题在于equals没有被重新定义,所以当map second迭代并遍历桶时,看看是否存在一个使得second.equals(k)为true的对象k ,它将不会找到任何作为second.equals(first)将是false

希望很明显


像HashMap和HashSet这样的集合使用对象的散列码值来确定对象应该如何存储在集合中,并再次使用散列码来帮助定位集合中的对象。

散列检索是一个两步过程。

  • 找到合适的存储桶(使用hashCode())
  • 在存储区中搜索正确的元素(使用equals())
  • 这里是一个小例子,为什么我们应该忽略equals()和hashcode()。考虑一个Employee类,它有两个字段age和name。

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

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

    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
    

    现在你能明白为什么如果两个对象被认为是相等的,他们的hashcode也必须相等? 否则,你永远无法找到该对象,因为在Object类中的默认hashcode方法实际上总是为每个对象都提供一个唯一的编号,即使equals()方法被重写以至于两个或多个对象被认为是平等的。 如果它们的哈希码不能反映这些对象,它们的平等程度如何。 再来一次:如果两个对象相等,那么它们的哈希码也必须相等。


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


    来自Effective Java,Joshua Bloch

    通过一致地定义equals()hashCode() ,可以提高类的可用性作为基于散列的集合中的键。 正如hashCode的API文档所解释的那样:“为了散列表的好处而支持此方法,例如java.util.Hashtable提供的方法。”

    关于如何高效地实现这些方法的问题,最好的答案是建议您阅读Effective Java的第3章。

    链接地址: http://www.djcxy.com/p/76107.html

    上一篇: Why do I need to override the equals and hashCode methods in Java?

    下一篇: Why do we need synchronized ArrayLists when we already have Vectors?