Thursday, April 5, 2012

Equals and HashCode in Java

Dear reader,
I have already written equals() and hashCode() as a part of "How HashMap works in Java", however writing
it again by making very simple example. HashMap related stuff can be seen in the below link:
http://deepakmodi2006.blogspot.in/2012/03/hashmap-in-java.html

Below are the points to remember:
    1) These two methods are defined in java.lang.Object class.
    2) If two objects are equal using equals() method (not ==) then their hashCode must be same.
    3) If hashCode are same for two or more than two objects, they may not be equal using equals() method.
    4) Overridding both methods in your API is not mandatory, you can override any one method.
    
    5) At last written the Contract based on what both method works as specified in Java Doc.
    6) Also make sure don't use random number generator in hashCode method, as it generates unexpected number, 
       every time you run the code and you will not be able to generate same hashCode for the same object passed.
       I mean suppose we generate hashCode() using a class Person's object based on this:
   
       public int hashCode(){
         return (int)Math.random()+firstName.hashCode()+lastName.hashCode();
       }
   
       Now for name "Deepak Modi" and "Deepak Kumar", hashCode should be different. Of course it is different here 
       in first run of the program. But when you run the program again without making any changes, you will get again 
       different hashCode what you generated for "Deepak Modi" and "Deepak Kumar" earlier. Java Spec don't say this. 
   

Please see below example:
===================================================
import java.util.HashMap;
import java.util.Map;

public class EqualsClass {
    String name;
    int id;
    public EqualsClass(){}
    public EqualsClass(String name, int id){
        this.id=id;
        this.name=name;
    }    
    @Override
    public int hashCode(){
        return 34;
    }
    public static void main(String[] args) {
        Map m=new HashMap();
        m.put("A", "65");
        m.put("B", "66");
        m.put("C", "67");
        System.out.println("Initially Map contains: "+m);

        EqualsClass p1=new EqualsClass("Deepak1",1);
        EqualsClass p2=new EqualsClass("Deepak2",2);
        m.put(p1,"Deepak1");
        m.put(p2,"Deepak2");

        System.out.println("After adding our Objects, Map contains: "+m);
        System.out.println("First object hashCode: "+p1.hashCode());
        System.out.println("Second object hashCode: "+p2.hashCode());
        System.out.println("p1.equals(p2): "+p1.equals(p2));
        System.out.println("p1==p2: "+(p1==p2));
    }
}
//Output:
Initially Map contains: {A=65, C=67, B=66}
After adding our Objects, Map contains: {EqualsClass@22=Deepak2, EqualsClass@22=Deepak1, A=65, C=67, B=66}
First object hashCode: 34
Second object hashCode: 34
p1.equals(p2): false
p1==p2: false

NOTE: Analyze the above output. HashCode of both objects are same even then they are not equal.
      I have written only hashCode() method and not equals(), it runs fine. Overridding both are not mandatory.
      Now I will add equals() method and then see:

=============
import java.util.HashMap;
import java.util.Map;

public class EqualsClass {
    String name;
    int id;
    public EqualsClass(){}
    public EqualsClass(String name, int id){
        this.id=id;
        this.name=name;
    }
    @Override
    public boolean equals(Object obj){
        return true;
    }
    @Override
    public int hashCode(){
        return 34;
    }
    public static void main(String[] args) {
        Map m=new HashMap();
        m.put("A", "65");
        m.put("B", "66");
        m.put("C", "67");
        System.out.println("Initially Map contains: "+m);

        EqualsClass p1=new EqualsClass("Deepak1",1);
        EqualsClass p2=new EqualsClass("Deepak2",2);
        m.put(p1,"Deepak1");
        m.put(p2,"Deepak2");

        System.out.println("After adding our Objects, Map contains: "+m);
        System.out.println("First object hashCode: "+p1.hashCode());
        System.out.println("Second object hashCode: "+p2.hashCode());
        System.out.println("p1.equals(p2): "+p1.equals(p2));
        System.out.println("p1==p2: "+(p1==p2));
    }
}
//Output:
Initially Map contains: {A=65, C=67, B=66}
After adding our Objects, Map contains: {EqualsClass@22=Deepak2, A=65, C=67, B=66}
First object hashCode: 34
Second object hashCode: 34
p1.equals(p2): true
p1==p2: false
---------------      
NOTE: Analyze the above output. I have made HashCode of all objects same and equals() method is returning true in 
      all the cases. Result: All objects are treated as equal. Also the last object in Map overrides the previous 
      Key in Map.

equals() Method contract: The equals method implements an equivalence relation:
 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.      
 6. It is necessary to override the hashCode method whenever this equals method is overridden, so as to maintain the 
    general contract for the hashCode method, which states that equal objects must have equal hash codes.       
 7. If two references are pointing to same object, I mean if x==y, then they x.equals(y) will result true.    
      

hashCode() Method contract: Returns a hash code value for the object. This method is supported for the benefit of 
    HashTables/HashMaps. The general contract of hashCode is:
 1. 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.
    Hence we should avoid using Math.random() or Random Class API for generating hashCode() as random will generate random
    values for the same object everytime we run to fetch hashCode().
    
 2. 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. If two objects are un-equal according to the equals(java.lang.Object) 
    method, then calling the hashCode method on each of the two objects may produce same integer results. However, the 
    programmer should be aware that producing distinct integer results for un-equal objects will improve the performance of 
    HashTables/HashMaps.
    
 3. As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for 
    distinct objects. This is typically implemented by converting the internal address of the object into an integer.     

--------------
So finally an example of correct implementation:
-----------------------
public class Test {
    private int num;
    private String data;

    public boolean equals(Object obj) {
        if(this == obj)
            return true;
        if((obj == null) || (obj.getClass() != this.getClass()))
            return false;

        //Now Object must be Test at this point as rest all are filtered above
        Test test = (Test)obj;
        return num == test.num &&
               (data == test.data ||
                (data!=null && data.equals(test.data)) 
               );
    }
    public int hashCode() {
        int hash = 7;
        hash = 31 * hash + num;
        hash = 31 * hash + (null == data ? 0 : data.hashCode());
        return hash;
    }
    //Other methods
}

NOTE: A common loop hole people use in equals() method like "instanceof" operator as below:
      Always use this: 
            (obj.getClass() != this.getClass())) return false;  //Use this
      
      As compare to: 
            if(!(obj instanceof Test)) return false;  //Never use this

      This is because, the first condition ensures that it will return false if the argument is a subclass of the class 
      Test. However, in case of the second condition (instanceof) it returns true. The instanceof operator condition fails 
      to return false if the argument is a subclass of the class Test. Thus, it might violate the symmetry requirement of 
      the contract. The instanceof check is correct only if the class is final, so that no subclass would exist. 
      
-------------------------END----------------------------    

No comments:

Post a Comment