Monday, March 21, 2016

Overriding equals And hashCode method in java

package com.bhoopendra.example;

public class Test { 
         private int num = 0;
         private String str = null; 
         Test(String str, int num) {
                   this.str = str; 
                  this.num = num; 
         }          
@Override         
 public boolean equals(Object obj) {
                   if (obj == this) { 
                            return true; 
                  }  
                 if (obj == null || obj.getClass() != this.getClass()) {
                             return false;
                   }                  
                Test obj2 = (Test) obj;
                   return (this.num == obj2.num && this.str.equals(obj2.str));
          }                  
 @Override    
   public int hashCode() { 
                   int hash = 7 ; 
                  int result = 31*hash + num; 
                  result = 31* result + (str ==null ?0: str.hashCode());
                   return result;                            
}

}

Lets take a simple class example.


Now let's take hashcode method first. As we all know that one has to override hashcode method along with equals method. The reason being simple .I am quoting Joshua Bloch . He says "

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."

Lets summarize the contract from Java specification :-

  • Whenever it is invoked on the same object more than once during an execution of an 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(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

    A good hash functions should return unequal hashcode for unequal objects. Ideally , a hash function should distribute  any reasonable collection of unequal instances uniformally across all possible values.

    Now lets analyze our hashcode finction for each line :

    @Override
        public int hashCode() {
    int hash = 7 ;
    int result = 31*hash + num;
    result = 31* result + (str ==null ?0: str.hashCode());
    return result;

    }

    Lets look at the line which is highlighted
      int hash =7;

    Idea behind using this non zero initialization is to affect hashvalue which would have rather unaffected any or all of the subsequent steps after this step and before returning any value have result in zero. Surely , more common hashcode values. Hence it would increase the chance of hash code collision. You  could have taken 7 or 17. I would prefer prime number here.  Now let's come to mutliplication factor . why 31 ? and why no 2, 4 ,6 10 etc.  31 is chosen because it is an odd prime.If it were even and the multiplication overflowed, the information would be lost, as multiplication by 2 is equivalent  to shifting. Advantage of using 31 is less clear, but it is done traditionally. A nice property of 31 is that multiplication can be replaced by shift and subtraction for better performance.

                 31*i =  i << 5 -i;
    Modern VMs do this sort of optimisation automatically.

    Now, I am presenting here some tips to write hashcode method which uses different types of data types.

    Step-1 : Store some non zero constant value in some variable say, result.
       e.g              int result = 17;

    Step-2 : For each significant field  f ( all those attributes which you want to take into account ) in your object compute hash code.
     2.a. If f  is boolean
           result += 31 * (f ?1:0) + result;
     2.b. If  f is  byte, char, short or int , compute (int) f;
            result  += 31 * ((int) f);
     2.c. If f is long, compute (int ) (f ^(f >>>32));
            result += 31 *  (int ) (f ^(f >>>32));
     2.d. If  is float , compute  Float.floatToIntBits(f)
            result += 31 *  Float.floatToIntBits(f);
    2.e. If f is double , compute Double.doubleToLongBits(f) and then hash the resulting long                       according to step 2.c
               long x =  Double.doubleToLongBits(f);
               result += 31 *  (int ) (x^(x>>>32));
    2.f. If field is an array, treat it as if each element were a separate field and then compute hash code of       each element by applying above rules recursively. Either way one can also use Arrays.hashcode           method.

    2.g. If the field is an object reference and this class’s equals method compares the field by recursively        invoking equals, recursively invoke   on the field. If a more complex comparison is required,              compute a “canonical representation” for this field and invoke hashCode on the canonical
           representation. If the value of the field is null, return 0 (or some other constant, but 0 is                        traditional).

    To summarize above result, have a look at the sample class written. which takes into account all above rules/tips.



    package com.bhoopendra.examples;

    public class Test {
       
           private int num = 0;
           private String str = null;
           private long longField= 7L;
           private float salary = 200.56f;
           private boolean isManager = false;
           private Object myObj = new Object();
           private short teamsize = 7;
           private char sex = 'M';
           private double ppfMoney = 30000000000d;

           Test(String str, int num) {
                  this.str = str;
                  this.num = num;
           }

           @Override
           public int hashCode() {
                  final int prime = 31;
                  int result = 1;
                  result = prime * result + (isManager ? 1231 : 1237);
                  result = prime * result + (int) (longField ^ (longField >>> 32));
                  result = prime * result + ((myObj == null) ? 0 : myObj.hashCode());
                  result = prime * result + num;
                  long temp;
                  temp = Double.doubleToLongBits(ppfMoney);
                  result = prime * result + (int) (temp ^ (temp >>> 32));
                  result = prime * result + Float.floatToIntBits(salary);
                  result = prime * result + sex;
                  result = prime * result + ((str == null) ? 0 : str.hashCode());
                  result = prime * result + teamsize;
                  return result;
           }

           @Override
           public boolean equals(Object obj) {
                  if (this == obj)
                         return true;
                  if (obj == null)
                         return false;
                  if (getClass() != obj.getClass())
                         return false;
                  Test other = (Test) obj;
                  if (isManager != other.isManager)
                         return false;
                  if (longField != other.longField)
                         return false;
                  if (myObj == null) {
                         if (other.myObj != null)
                               return false;
                  } else if (!myObj.equals(other.myObj))
                         return false;
                  if (num != other.num)
                         return false;
                  if (Double.doubleToLongBits(ppfMoney) != Double.doubleToLongBits(other.ppfMoney))
                         return false;
                  if (Float.floatToIntBits(salary) != Float.floatToIntBits(other.salary))
                         return false;
                  if (sex != other.sex)
                         return false;
                  if (str == null) {
                         if (other.str != null)
                               return false;
                  } else if (!str.equals(other.str))
                         return false;
                  if (teamsize != other.teamsize)
                         return false;
                  return true;
           }  
       
    }