Friday, January 28, 2022

equals() And hashCode() Methods in Java

In this post we'll see what are equals() and hashCode() methods in Java and why do we need to override these methods.


equals() and hashCode() methods in Java

In Java equals() and hashCode() methods are present in the java.lang.Object class. These two methods are used for making inferences about an object's identity or in simpler language to reach to a decision whether the two compared objects are equal or not.

The default implementation of equals() method in the Object class is a simple reference equality check.

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

The default implementation of hashCode() in the Object class just returns integer value of the memory address of the object.

Usage of hashCode() and equals() methods in Java

  • hashCode()- This method is used to get a unique integer value for a given object. We can see it's use with hash based collections like HashTable or HashMap where hashCode() is used to find the correct bucket location where the particular (key, value) pair is stored.

    Refer How HashMap internally works in Java to know more about it.

  • equals()- equals() method is used to determine the equality of two objects.

When do we need to override hashCode() and equals() methods

If you are using a custom object as key in a hash based collection it becomes very important to override hashCode() and equals() methods in Java.
Also in case of ORM like Hibernate when a detached instance is re-attached to a session, we need to make sure that the object is same.

In such cases we can't rely on the default implementation provided by the Object class and need to provide custom implementation of hashCode() and equals() method.

But I never had the need to override hashCode() and equals() methods

Most of the time we use HashMap when it comes to hash based collections. With in HashMap, we mostly use primitive wrapper class like Integer or String class, as key. These classes are immutable and provide their own proper implementation of hashCode() and equals() methods, thus these classes, on their own are, good hash keys.

Things become tricky when we are using any custom object as key, in that case it becomes very important to override these 2 methods to make sure that we really get the object we want.

Let's say you are using some custom class object as a key in HashMap and that class did not override equals() and hashCode(), what will happen in that case?

In that case we would not be able to reliably retrieve the associated value using that key, unless we used the exact same class object as key in the get() method as we stored using the put() method.

Now, you may wonder why using the same instance is it possible? Because in the case we are not overriding and providing the implementation of equals() and hashCode(), Object class' equals() and hashCode() methods will be used and recall that in Object class equals() method implementation is simple reference equality check thus having the same instance may satisfy the Object class' equals() method.

Let's see an example where custom object is used but hashCode and equals are not overridden and custom implementation is not given.

class Customer {
  private int customerID;
  private String firstName;
  private String lastName;
  public Customer(int customerID, String firstName, String lastName) {
    super();
    this.customerID = customerID;
    this.firstName = firstName;
    this.lastName = lastName;
  }  
}

public class HashCodeDemo {
  public static void main(String[] args) {
    Map<Customer, String> m = new HashMap<Customer, String>();
    Customer cust = new Customer(1, "Roger", "Cox");
    m.put(cust,"Roger Cox");
    // retrieving using another instance
    System.out.println(m.get(new Customer(1, "Roger", "Cox")));
    // retrieving using same instance
    System.out.println(m.get(cust));               
  }
}

Output

null
Roger Cox

It can be seen how; when the instance is changed the value is not retrieved even when the same values for the customer object are given as key. But when we use the same instance then it is able to fetch. But that's not very convenient and in a big application we are not expected to keep the same instance and use it whenever we want to retrieve the value. So enter the equals() and hashCode() methods.

Same example with hashCode and equals implemented

class Customer {
  private int customerID;
  private String firstName;
  private String lastName;

  public Customer(int customerID, String firstName, String lastName) {
    super();
    this.customerID = customerID;
    this.firstName = firstName;
    this.lastName = lastName;
  }
      
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + customerID;
    result = prime * result
            + ((firstName == null) ? 0 :   firstName.hashCode());
    result = prime * result
            + ((lastName == null) ? 0 : lastName.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
        return false;
    Customer other = (Customer) obj;
    if (customerID != other.customerID)
      return false;
    if (firstName == null) {
      if (other.firstName != null)
        return false;
    } else if (!firstName.equals(other.firstName))
        return false;
    if (lastName == null) {
      if (other.lastName != null)
        return false;
    } else if (!lastName.equals(other.lastName))
        return false;
    return true;
  }
}
public class HashCodeDemo {
  public static void main(String[] args) {
    Map<Customer, String> m = new HashMap<Customer, String>();
    Customer cust = new Customer(1, "Roger", "Cox");
    m.put(cust,"Roger Cox");
    // retrieving using another instance
    System.out.println(m.get(new Customer(1, "Roger", "Cox")));
    // retrieving using same instance
    System.out.println(m.get(cust));               
  }
}

Output

Roger Cox
Roger Cox

Now it will use the custom implementation of equals to see if two instances of Customer class are logically equal. So in both cases, using the same instance or different instance, we are able to get the value.

To see a discussion on why 31 is recommended to be used as a multiplier in hashCode refer-
http://stackoverflow.com/questions/299304/why-does-javas-hashcode-in-string-use-31-as-a-multiplier

And if you are worried that you have to write so much to implement hashCode and equals, don't worry! IDEs provide an option to do that for you. In eclipse IDE that option to generate hashcode and equals method has this path source - Generate hashCode() and equals().

Rules for implementing equals() and hashCode() in Java

There are certain restrictions on the behavior of equals() and hashCode() methods in Java, which can be seen in the Javadocs for Object class- http://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#equals(java.lang.Object)

For equals() method the JavaDocs say-

  • It is reflexive: for any non-null reference value x, x.equals(x) should return true.
  • It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  • It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
  • For any non-null reference value x, x.equals(null) should return false.

And the general contract of hashCode is-

  • Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
  • If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
  • It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.

To explain this contract a little more-

The first point says that the hashCode() method must consistently return the same integer. In case a mutable object is used as key we have to make sure that its state does not change. If a key's hashCode changes while used in a Collection get and put will give some unpredictable results.

According to the second point if 2 objects Obj1 and Obj2 are equal according to their equals() method then they must have the same hash code too. Though the vice-versa is not true that is if 2 objects have the same hash code then they do not have to be equal too.

According to the third point if 2 objects are not equal according to their equals() method they still can have the same hash code. That is known as hash collision.

That's all for this topic equals() And hashCode() Methods in Java. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. How to Loop Through a Map in Java
  2. How ArrayList Works Internally in Java
  3. Difference Between Comparable and Comparator in Java
  4. Difference Between equals() Method And equality Operator == in Java
  5. Java Collections Interview Questions And Answers

You may also like-

  1. Fail-Fast Vs Fail-Safe Iterator in Java
  2. Marker Interface in Java
  3. Why no Multiple Inheritance in Java
  4. Lambda Expressions in Java 8
  5. Transaction Management in Java-JDBC
  6. finally Block in Java Exception Handling
  7. Check Given Strings Anagram or Not Java Program
  8. Dependency Injection in Spring Framework

2 comments: