Why do I need to override the equals and hashCode methods in Java?

Recently I read through this Developer Works Document.

The document is all about defining hashCode() and equals() effectively and correctly, however I am not able to figure out why we need to override these two methods.

How can I take the decision to implement these methods efficiently?


Joshua Bloch says on Effective Java

You must override hashCode() in every class that overrides equals(). Failure to do so will result in a violation of the general contract for Object.hashCode(), which will prevent your class from functioning properly in conjunction with all hash-based collections, including HashMap, HashSet, and Hashtable.

Let's try to understand it with an example of what would happen if we override equals() without overriding hashCode() and attempt to use a Map .

Say we have a class like this and that two objects of MyClass are equal if their importantField is equal (with hashCode() and equals() generated by eclipse)

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

}

Override only equals

If only equals is overriden, then when you call myMap.put(first,someValue) first will hash to some bucket and when you call myMap.put(second,someOtherValue) it will hash to some other bucket (as they have a different hashCode ). So, although they are equal, as they don't hash to the same bucket, the map can't realize it and both of them stay in the map.


Although it is not necessary to override equals() if we override hashCode() , let's see what would happen in this particular case where we know that two objects of MyClass are equal if their importantField is equal but we do not override equals() .

Override only hashCode

Imagine you have this

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

If you only override hashCode then when you call myMap.put(first,someValue) it takes first, calculates its hashCode and stores it in a given bucket. Then when you call myMap.put(second,someOtherValue) it should replace first with second as per the Map Documentation because they are equal (according to the business requirement).

But the problem is that equals was not redefined, so when the map hashes second and iterates through the bucket looking if there is an object k such that second.equals(k) is true it won't find any as second.equals(first) will be false .

Hope it was clear


Collections such as HashMap and HashSet use the hashcode value of an object to determine how the object should be stored in the collection, and the hashcode is used again to help locate the object in the collection.

Hashing retrieval is a two-step process.

  • Find the right bucket (using hashCode())
  • Search the bucket for the right element (using equals() )
  • here is a small example why we should overrride equals() and hashcode().Consider an Employee class which has two fields age and 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;
            }
         */
    }
    

    Now create a class, insert Employee object to a HashSet and test whether that object is present or not.

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

    It will print

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

    Now remove the commented line from hashcode() and execute and now it will print

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

    Now can you see why if two objects are considered equal, their hashcodes must also be equal? Otherwise, you'd never be able to find the object since the default hashcode method in class Object virtually always comes up with a unique number for each object, even if the equals() method is overridden in such a way that two or more objects are considered equal. It doesn't matter how equal the objects are if their hashcodes don't reflect that. So one more time: If two objects are equal, their hashcodes must be equal as well.


    You must override hashCode() in every class that overrides equals(). Failure to do so will result in a violation of the general contract for Object.hashCode(), which will prevent your class from functioning properly in conjunction with all hash-based collections, including HashMap, HashSet, and Hashtable.


    from Effective Java, by Joshua Bloch

    By defining equals() and hashCode() consistently, you can improve the usability of your classes as keys in hash-based collections. As the API doc for hashCode explains: "This method is supported for the benefit of hashtables such as those provided by java.util.Hashtable ."

    The best answer to your question about how to implement these methods efficiently is suggesting you to read Chapter 3 of Effective Java.

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

    上一篇: ConcurrentHashMap类似于CopyOnWriteArrayList

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