Friday, December 23, 2011

Hashcode & equals

Contract between hashcode and equals as per Java specification -

* 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 hashtables.

--------------------------------------------
Sample Buggy code to show bug if same fields are not used to compute equals and hashCode :
--------------------------------------------
package com.examples;

public final class Box {

private final int ssn;
private final int age;

public Box(int ssnval, int ageval) {
ssn = ssnval;
age = ageval;
}

// Magic number 31 (odd and prime) as seed just like java String.
public int hashCode() {
return 31 + ssn;
}

public boolean equals(Object that) {

if (this == that) {
return true;
}

if (! (that instanceof Box) ) {
return false;
}

if ((this.ssn == ((Box)that).ssn) && (this.age == ((Box)that).age)) {
return true;
}

// Bug in code as there is a mismatch of fields between equals and hashcode.
return false;
}

}

package com.examples;

import java.util.*;

public class Test {

public static void main(String[] args) {
Box b1 = new Box(10, 20);
Box b2 = new Box(10, 25);

if (b1.hashCode() == b2.hashCode()) {
System.out.println("Box objects b1 & b2 - hashcodes are same");
} else {
System.out.println("Box objects b1 & b2 - hashcodes are NOT same");
}

if (b1.equals(b2)) {
System.out.println("Box objects b1 & b2 - equals are same");
} else {
System.out.println("Box objects b1 & b2 - equals are NOT same");
}

Map boxmap = new HashMap();
boxmap.put(b1, "TestVal");

String val1 = boxmap.get(b1);
String val2 = boxmap.get(b2);
System.out.println("Val1 = " + val1);
System.out.println("Val2 = " + val2);

}

}

Output demonstrating the bug : val2 is null because of broken contract.
-------------------------------
Box objects b1 & b2 - hashcodes are same
Box objects b1 & b2 - equals are NOT same
Val1 = TestVal
Val2 = null


The above example clearly shows that Java collection uses hashCode first to go to the appropriate bucket. Then uses equals() to see if object matches.

Bug fix: Change hashCode() to use 31 + ssn + age.

*** Note that the above sample uses Immutable Box object demonstration - all final, private and one-shot object construction.


References:
------------
* Implementing HashCode - http://www.javapractices.com/topic/TopicAction.do?Id=28

* http://tech-read.com/2009/02/12/use-of-hashcode-and-equals

No comments: