menu

Thursday, November 27, 2014

Using hashCode(), equals() and toString() Methods in Java

Introduction:

     The Object class being the super class of all the classes in Java has the methods hashCode(), equals(Object) and toString() addition to some other methods like clone(), getClass() and so on. I'll try to illustrate the important points of the use of hashCode(), equals() and toString() methods today.

Default Implementation:

     By default hashCode() is a native method with a signature "public native int hashCode()". The Java doc. says that 
* It is used to return a hash code value of an object to be used in hash tables like HashMap and HashSet which we introduce in later writes. 
* The hashCode() must return the same integer value if it's invoked more than once, however it's not required to be the same value with another execution. 
* You have to obey the contract of "equal objects must have equal hash codes", however two unequal objects also can have the same hash code which decrease the performance of hash tables.
* It is not a must that the hashcode method of Object class return distinct integers for different objects so don't rely on that.
* It is typically implemented to return the internal address of the object converting to an integer, but it's not a requirement of Java programming language and it's JVM dependent.

Overriding Default Implementation:

     If you override the hashCode() method you have to rely on the contract of "equal objects must have equal hash codes" and define the fields that you want to be included in the hashCode and equals implementations.Then you should use a prime number as a multiplier and use the primitive fields adding to the result, and use the hashCode() methods of Object fields like String's hashCode() implementation used in the Example 1. Many of the IDE's have the ability of creating a reasonable hashCode() and equals() implementations as well as the toString() method. For example in eclipse you can use Source -> Generate hashCode() and equals(). Below is an example of hashCode(), equals() and toString() implementations created for a String (named innerStr) and an int(innerInt) using the eclipse.
For the equals() method, you should consider the null conditions, direct equality of references, runtime classes of the object instance (if you want the same runtime class for both instance), and the the equality of the required fields using == for primitives and using the correspending equals() methods for the Objects. The default equals method just compare the references not the values as can be shown below.

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

    }

Example 1:

      @Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + innerInt;
result = prime * result + ((innerStr == null) ? 0 : innerStr.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;
HashSetEx other = (HashSetEx) obj;
if (innerInt != other.innerInt)
return false;
if (innerStr == null) {
if (other.innerStr != null)
return false;
} else if (!innerStr.equals(other.innerStr))
return false;
return true;
}

@Override
public String toString() {
return "Test [innerStr=" + innerStr + ", innerInt=" + innerInt + "]";

}

Using System.identityHashCode(Object o) Method:

     Even if you override the hashCode() method, you can still get the default behaviour's result using the System.identityHashCode method. It returns as if you don't override the hashCode() method. Remember again that, the default behaviour need not to be a distinct integer for all different objects and also need not to be an integer indicating the internal address of the object.

Example 2:

     Assuming we use the default toString() implementation as below;

    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    public class Test {

private String innerStr;
private Integer innerInt;

public Test(String innerStr, int innerInt) {
this.innerInt = innerInt;
this.innerStr = innerStr;
}
public static void main(String[] args) {
Test test = new Test("a", 1);
System.out.println(test);
System.out.println(Integer.toHexString(System.identityHashCode(test)));
}
    }

The output will be as below. 

hash.Test@38da9246
38da9246

Now, if you override hashCode() as below;

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((innerInt == null) ? 0 : innerInt.hashCode());
result = prime * result + ((innerStr == null) ? 0 : innerStr.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;
Test other = (Test) obj;
if (innerInt == null) {
if (other.innerInt != null)
return false;
} else if (!innerInt.equals(other.innerInt))
return false;
if (innerStr == null) {
if (other.innerStr != null)
return false;
} else if (!innerStr.equals(other.innerStr))
return false;
return true;
}

And run again, you will get the following result;

hash.Test@441
7448bc3d

As you see we get the default behaviour with the System.identityHashCode method. If we run again we get the same hashCode with the overrided hashCode() method, but get different hashCode with the System.identityHashCode method as you see below.

hash.Test@441
68c884e

See, we use the hex representation of the returned integer of hashCode() method as it's used in the default toString() implementation.
   
Conclusion:

     We see some important points of using hashCode(), equals() and toString() methods. You should now what the Java doc. says and now where you can rely on or not to the behaviour of that methods.