Wednesday 21 March 2012

How and Why to override the equals and hashCode methods in Java

Object class has following five non final methods.

1. clone()
2. equals(Object obj)
3. finalize()
4. hashCode()
5. toString()

Every Java class has Object as a superclass so by default all above methods are provided in every java class. In this article we will discuss in detail the concepts of equals() and hashCode() method. We will discuss why it is necessary to override these methods and how we can override them.

Default implementation of equals() in Object class

The default version of equals method present in the Object class has the same behavior as the == operator which in turn simply checks if two reference variables are referring the same objects.Consider x and y, two object references, then x.equals(y) comparing the memory addresses of the objects referenced by x and y (i.e. not the data of the objects).
See the example given below

public class Main {

    public static void main(String args[]){

        Student s1 = new Student("Suresh");
        Student s2 = new Student("Suresh");
        Student s1_copy = s1;
        System.out.println("s1.equals(s2)=" + s1.equals(s2));
        System.out.println("s1.equals(s1_copy)=" + s1.equals(s1_copy));

    }

}

class Student {

    String name;

    Student(String name) {
        this.name = name;
    }

}

The output is

s1.equals(s2)=false
s1.equals(s1_copy)=true

You can see that s1.equals(s2) returns false even if both s1 and s2 contain the same name 'Suresh'.The reason is that Student class is using the Object class provided equals method which checks the referential equality,here it returns false because s1 and a2 are stored at different memory locations.The s1.equals(s1_copy) returns true because s1 and s1_copy is pointing to the same object ,same memory location.

Why to override equals() method

The Object class provided equals method comparing the memory addresses of the objects being referenced by the two reference variables being used with equals method.The general use of the equals method is to compare the data of the objects not its memory addresses.So in this case you have to override the equals method.

NOTE: One exception is that classes like String,Date and Wrapper classes (Integer,Float etc.) override the equals method it inherited from the Object class and implemented logic to compare data of the objects.

Overriding equals method in Java


One should follow the following conditions while overriding equals method.
  1. It is reflexive: for any reference value x, x.equals(x) should return true.
  2. It is symmetric: for any reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  3. It is transitive: for any 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.
  4. It is consistent: for any 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 object is modified.
  5. For any non-null reference value x, x.equals(null) should return false.  
Let us discuss the above rules with one simple example
public class Main {

public static void main(String args[]){

Student s1 = new Student("Suresh");
Student s2 = new Student("Suresh");
System.out.println("s1.equals(s2)=" + s1.equals(s2));

}

}
class Student {
 String name;

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

    @Override
    public boolean equals(Object obj) {
        if (this == obj) ----------->Reflexive rule
            return true;
        if (obj == null) -----------> null rule
            return false;
        if (getClass() != obj.getClass()) ------->symmetric rule
            return false;
        Student other = (Student) obj; -------->Casting obj to Student
        if (name == null) { -------------------->Comparing data of both objects
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
}

The output is

s1.equals(s2)=true ( Remember that in the above example it was false :) )

Let us check that how the Student class overridden equals method is following the above mentioned rules.

1) Reflexive

For any reference value x, x.equals(x) should return true can be achieved using the if condition if(this==obj) return true.

2) Symmetric

For any reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true can be achieved using the if condition if(getClass()!=obj.getClass()) return false.

The getClass method (use above) is one of the methods of the Object class and is inherited by all classes from Object. It returns a reference to the Class class for the class-type of the reference it is called on. There is only one Class class reference for a particular Class - this is known as a singleton. All instances of the same Class share the same Class class reference. If you call getClass on any two references of the same type you'll get the same Class class reference value.

If you use instanceof instead of getClass, you can't always ensure the symmetric property.See one scenario

The class Fruit extends Apple and take objects as Fruit f and Apple a.

f.equals(a) =true
a.equals(f) =false

The symmetric property is failed here.So always use getClass instead of instanceof operator while overriding the equals method.

3) null rule

For any non-null reference value x, x.equals(null) should return false can be achieved by the if condition if(obj==null) return false.

NOTE: Other two remaining rules will be obeyed implicitly if you follow these three rules.

Inheritance and the equals Method

The sub class can reuse the base class overridden equals method while overriding the inherited equals method from base class.One example

public class Main {

    public static void main(String args[]){

        Sub obj1 = new Sub(10, 20);
        Sub obj2 = new Sub(10, 20);

        System.out.println("obj1.equals(obj2)=" + obj1.equals(obj2));

    }

}

class Base {

    int x;

    Base(int x) {
        this.x = x;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + x;
        return result;
    }

    @Override
    public boolean equals(Object obj) {

        if (this == obj)
             return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Base other = (Base) obj;
        if (x != other.x)
            return false;
        return true;
    }

}

class Sub extends Base {
    int y;

    Sub(int x, int y) {
        super(x);
        this.y = y;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result + y;
        return result;
    }

    @Override
    public boolean equals(Object obj) {

        if (!super.equals(obj))
            return false;
        Sub other = (Sub) obj;
        if (y != other.y)
            return false;
        return true;
    }

}


The output is

obj1.equals(obj2)=true

Notice the absence of null check,reflexive check and class-type check in the sub class equals implementation.If you do them here your code will be logically correct but you will be duplicating logic and wasting CPU cycles.

You can see that Base class and Sub class are loosely coupled here.That means you can modify the Base class equals method without affecting the Sub class equals method and vice versa.

Please visit the following links to learn about hashCode method

 Overriding hashCode method in Java

Why override equals() and hashCode() methods of objects as Map keys in Java


1 comment: