Java Burning Interview question – Mystery behind equals, ==, and hashcode
equals() of Object class and logic operator “==” puzzle me always. I decided to write this article after finding many arguments and questions on the same topic.
Lets start with a simple example;
System.out.println("Amit".hashCode());
System.out.println("Amit".hashCode());
String name = "Amit";
System.out.println(name.hashCode());
System.out.println(name == "Amit");
System.out.println(name.equals("Amit"));
String[] nameList = {"Amit","Arti"};
System.out.println(nameList[0].hashCode());
System.out.println(name == nameList[0]);
System.out.println(name.equals(nameList[0]));
nameList[0] = new String("Amit");
System.out.println("After initializing new memory location");
System.out.println(name == nameList[0]);
System.out.println(name.equals(nameList[0]));
2044535
2044535
2044535
true
true
2044535
true
true
After initializing new memory location
false
true
Conclusion:
- Logical operator “==” checks for reference not for value. So name == nameList[0] gives false after nameList[0] = new String(“Amit”);
- equals() checks for value not for reference.
- JVM allocates same memory location for same constants. So 2 different String variables having same value points to same memory location.
- hashCode() returns same value for two objects if obj1.equals(obj2) is true.
From Java Doc, the equals() method must exhibit the following properties:
- Symmetry: For two references, a and b, a.equals(b) if and only if b.equals(a)
- Reflexivity: For all non-null references, a.equals(a)
- Transitivity: If a.equals(b) and b.equals(c), then a.equals(c)
- Consistency with hashCode(): Two equal objects must have the same hashCode() value
* o.equals(null) must always return false.
Warning:
If A equlas B then A.hashcode must be equal to B.hascode but if A.hashcode equals B.hascode it does not mean that A must equals B.
So its better to override hashCode() to generate your own hashCode().
Short version of generating hash from Josh Bloch’s “Effective Java”;
- Create a int result and assign a non-zero value.
- For every field tested in the equals-Method, calculate a hash code c by:
- If the field f is a boolean: calculate (f ? 0 : 1)
- If the field f is a byte, char, short or int: calculate (int)f
- If the field f is a long: calculate (int)(f ^ f( >>> 32)
- If the field f is a float: calculate Float.floatToIntBits(f)
- If the field f is a double: calculate Double.doubleToLongBits(f) and handle the return value like every long value
- If the field f is an object: Use the result of the hashCode() method or 0 if f is a null referenence.
- If the field f is an array: See every field as separate element and calculate the hash value in a recursive fashion and combine the values as described next.
- Combine the hash value c with result with:
- Return result
result = 37 * result + c
Or use helper classes EqualsBuilder and HashCodeBuilder from the Apache Commons Lang library. An example:
public class Person {
private String name;
private int age;
// ...
public int hashCode() {
return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
// if deriving: appendSuper(super.hashCode()).
append(name).
append(age).
toHashCode();
}
public boolean equals(Object obj) {
if (obj == null)
return false;
if (obj == this)
return true;
if (obj.getClass() != getClass())
return false;
Person rhs = (Person) obj;
return new EqualsBuilder().
// if deriving: appendSuper(super.equals(obj)).
append(name, rhs.name).
append(age, rhs.age).
isEquals();
}
}
* The downside of this API is the cost of object construction every time you call equals and hashcode (unless your object is immutable and you precompute the hash)
System.identityHashCode(): Returns the same hash code for the given object as would be returned by the default method hashCode(), whether or not the given object’s class overrides hashCode(). It means if you don’t override hashCode() then System.identityHashCode(obj) == obj.hashCode() is true.
Collection
When using a hash-based Collection or Map such as HashSet, LinkedHashSet, HashMap, Hashtable, or WeakHashMap, make sure that the hashCode() of the key objects that you put into the collection never changes while the object is in the collection. The bulletproof way to ensure this is to make your keys immutable.
IdentityHashMap vs HashMap
Although IdentityHashMap and HashMap follows same data structure but IdentityHashMap compares keys as;
if(System.identityHashCode(k1) == System.identityHashCode(k1)){
if(k1 == k2){
:
}
}
while HashMap compares keys as;
k1 == null ? k2 == null : k1.equals(k2)
It means
- IdentityHashMap is faster than HashMap. It doesn’t check for quality of values but their hash code and reference.
- HashMap can use you override equals()
- If you don’t override equals() then both Map should work exactly same.
So when IdentityHashMap should be used?
The documentations says:
A typical use of this class is topology-preserving object graph transformations, such as serialization or deep-copying. To perform such a transformation, a program must maintain a “node table” that keeps track of all the object references that have already been processed. The node table must not equate distinct objects even if they happen to be equal. Another typical use of this class is to maintain proxy objects. For example, a debugging facility might wish to maintain a proxy object for each object in the program being debugged.
If you have some more points on this topic please comment.
views


No Comments