equals() and hashcode() methods in Java

In this post, we will discuss equals() and hashcode() methods defined in the Object class of Java. We will also learn how to use them while creating our own classes. Even though both of these methods have their own significance and yet they have a dependency on each other.

equals() and hashcode() methods are defined in the Object class which is the superclass of all classes, so by default, all of the classes in Java will inherit these methods.

 public native int hashCode();

 public boolean equals(Object obj) {
        return (this == obj);
 }

equals()

As per documentation in Object.class, this method indicates if two objects are equal or not

As per implementation in the Object class, for any non-null reference values, this method should return true if and only if they refer to the same object but we can override this method in our own class as per our need.

Below are the rules for a correct equals() method –

  • It is reflexive – x.equals(x) must be true.
  • It is symmetrical – x.equals(y) must be true if and only if y.equals(x) is also true.
  • It is transitive – If x.equals(y) is true and y.equals(z) is true, then x.equals(z) must also be true.
  • It is repeatable – Multiple calls on x.equals(y) return the same value.
  • It is cautious – x.equals(null) must return false rather than accidentally throwing a NullPointerException.

The general contract for the hashcode method states that equal objects must have equal hash codes. So it is generally necessary to override the hashcode method whenever the equals method is overridden.

hashcode()

As per documentation in the Object class, hashcode() returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by java.util.HashMap.

hashcode() method is supposed to return an int value that should uniquely identify different objects.

Below are the rules for a correct hashcode() method –

  • It is repeatable – hashCode(x) must return the same int when called repeatedly unless set methods have been called.
  • It is consistent with equality – If x.equals(y), then x.hashCode() must == y.hashCode().
  • Distinct objects should produce distinct hashCodes – If x.equals(y) is not true, then it is not required that x.hashCode() != y.hashCode(), but doing so may improve performance of hash tables (i.e., hashes may call hashCode() before equals() ).

How to use hashcode and equals

Let’s understand both these methods using an example:

public class Employee {

    private Integer employeeId;
    private String employeeName;

    public Employee(Integer employeeId, String employeeName) {
        this.employeeId = employeeId;
        this.employeeName = employeeName;
    }
    // getter and setter methods

}

Till now, we have not overridden the equals and hashcode method in our class, they are inherited from the Object class. Now we will test it by creating objects.

 Employee employee1 = new Employee(1, "Employee");
 Employee employee2 = employee1;  // employee2 is referring to employee1 object
 System.out.println(employee1.hashCode() == employee2.hashCode()); // true
 System.out.println(employee1.equals(employee2)); // true

In this example, employee1 is our first object and empolyee2 is referring to employee1 itself, so the hashcode() for employee1 and employee2 will be the same as both objects are referring to the same object.
equals() method will also return true because by default in the Object class, object references are checked and they are the same for both employee1 and employee2 objects

Let’s create a new Employee object ( employee3) and try again the same thing we did for employee2 and employee1 but this time with employee3 and employee1.

 Employee employee3 = new Employee(1, "Employee");
 System.out.println(employee1.hashCode() == employee3.hashCode()); // false
 System.out.println(employee1.equals(employee3)); // false

We have created a new Object employee3 using a new operator. So a new hashcode will be returned for this object. Due to this, the hashcode of employee1 and employee3 won’t match and the equals() method will also return false as both references are pointing to the different objects.

Now we have a fair understanding of the equals() and hashcode() method and now we will learn why we need to override these methods while creating our own objects in Java.

Why we need to override the equals() and hashcode() methods in Java

We will see the importance of overriding the equals() and hashcode() methods with some of the most used things in Java

Importance of overriding equals() method in List

List<Employee> employeeList = new ArrayList<>();
Employee employee1 = new Employee(1, "employee1");
Employee employee2 = new Employee(1, "employee2");
Employee employee3 = new Employee(1, "employee3");
employeeList.add(employee1);
employeeList.add(employee2);
employeeList.add(employee3);
Employee employee4 = new Employee(1, "employee1");
System.out.println(employeeList.contains(employee4)); // false

Here we have created an employee list and added 3 employeesa and then we have created a new object employee4 with same values as employee1.

In the next step, we have tested whether the list contains employee4 or not. Ideally, it should have returned true as we initially added employee1 in the list and both employee4 and employee1 are equal in their values but it returned false.
Because the list contains() method internally uses the equals() method of Employee class and it does not override the equals() method, so in this case, the equals() method of the Object class is used which just checks whether both the objects refer to the same object or not and don’t care about their values.

To overcome this we need to override the equals() method using some or all employee class properties to check whether two employees are equal or not. We have overridden the equals() method using the eclipse shortcut but you can create it manually too 😛

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

So, now, we have overridden the equals() method in the Employee class and used employee class properties to check for equality. Now, if we run the contains() method again, it will return true unlike earlier.

System.out.println(employeeList.contains(employee)); // true

It returns true as we are comparing class properties instead of references. In real problems too, we want to check object equality using properties instead of references.

Note: We have not overridden the hashcode() method yet. But as per general contract, we should override both hashcode and equals method together.

Importance of overrideing hashcode() and equals() methods in map

Map data structure internally uses hashcode() and equals() methods to store elements. Map data structure uses an array of a linked lists to store elements. Each element of this array is called a bucket.
If for multiple objects, we are getting the same hashcode and bucket number(It would depend on our hashcode implementation), then this known as Hash Collision. After the hashcode calculation, the map will generate a bucket number, and then using the equals() method, it will check whether the object is there or not in the linked list.

Note: In Java 1.8 this linked list has been replaced by the balanced tree for better performance.

  • If the element is present in the map, then in the case of get() operation it will return a value and in the case of put() operation, it will override the value.
  • If the element is not present in the map, then in the case of get() operation it will return null and in the case of put() operation, it will add an element in the map.

Example

In below example, we have created a manager map and added 3 employee to this map.

Map<Employee, String> managerMap = new HashMap<>();
Employee employee1 = new Employee(1, "employee1");
Employee employee2 = new Employee(1, "employee2");
Employee employee3 = new Employee(1, "employee3");
managerMap.put(employee1, "Manager1");
managerMap.put(employee2, "Manager2");
managerMap.put(employee3, "Manager3");
Employee employee4 = new Employee(1, "employee1");
System.out.println(managerMap.get(employee1)); // returns Manager1
System.out.println(managerMap.get(employee4)); // returns null

After that, we have created a new object employee4 same as employee1, and used the get() operation to get the manager name using employee1 and employee4 object.

For the employee1 object, it returns the manager name but for the employee4 object, it returns null. Though we are using the same employee properties as employee1 and also override the equals method, we need to check why this is happening?

The reason being we have not overridden the hashcode method. As mentioned earlier, maps use the hashcode method to calculate the bucket numbers and the employee class doesn’t have a hashcode method. In our case, the hashcode method of Object class is used which returns different hashcode for each new object created.

For employee1, new hashcode is generated and a new bucket number is calculated. That bucket does not contain that element, so it returns null.

Now we have to override the hashcode method and we have already overridden the equals() method earlier, else we have to override that method too. We are using eclipse to auto-generate the hashcode method.

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

System.out.println(managerMap.get(employee)); // Manager1

Now we run the get() operation again and this time we will get the manager name.

  • It is good to use immutable hash code keys so that they can be cached and map performance improves. String, Integer all these classes in java override hashcode and equals methods, that’s why we can use them as hashmap key.

References:
https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html
https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#equals(java.lang.Object)
https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#hashCode()

Liked the article? Share this on

Leave a Comment

Your email address will not be published.