If we work with Maps in java than the keys need to implement equals and hashcode - Right ? Actually Wrong.
The reason we need hashcode is all Map implementations that we know of are hash based implementations. The reason we always implement equals is that once inside a Map's bucket we need equals to identify the key accurately.
But can the Map work without equals ?Yes. If they key does not implement equals, it inherits the equals method from Object. Thus two keys will be equal if and only if they refer to the same location in memory. An Identity Equality.
This is where the IdentityHashMap makes an entrance. The map is a special implementation that only supports Identity based Equality.
Consider the class Age - a simple class with custom equals and hashcode method.
I then replaced the HashMap with an IdentityHashMap.
Where would we use this implementation ?
From the java docs:
The reason we need hashcode is all Map implementations that we know of are hash based implementations. The reason we always implement equals is that once inside a Map's bucket we need equals to identify the key accurately.
But can the Map work without equals ?Yes. If they key does not implement equals, it inherits the equals method from Object. Thus two keys will be equal if and only if they refer to the same location in memory. An Identity Equality.
This is where the IdentityHashMap makes an entrance. The map is a special implementation that only supports Identity based Equality.
Consider the class Age - a simple class with custom equals and hashcode method.
class Age {If I were to test the same against a map:
publicfinal Integer val;
public Age(Integer val) {
this.val = val;
}
@Override
publicint hashCode() {
return val.hashCode();
}
@Override
publicboolean equals(Object obj) {
returnthis.val.equals(((Age) obj).val);
}
}
publicstatic void main(String[] args) {The output:
Map<Age, String> map = new HashMap<Age, String>();
map.put(new Age(7), "seven");
System.out.println("One Element added: Size is " + map.size());
Age age7 = new Age(7);
map.put(age7, "seven Again");
System.out.println("Two Elements added: Size is " + map.size());
map.put(new Age(7), "Seven a Third time");
System.out.println("Three Elements added: Size is " + map.size());
System.out.println("Value recovered For 7 is : [ " + map.get(age7) + " ]");
}
One Element added: Size is 1As expected, the three keys all returned true for equals. Hence the map held just one entry. The final object added was retained.
Two Elements added: Size is 1
Three Elements added: Size is 1
Value recovered For 7 is : [ Seven a Third time ]
I then replaced the HashMap with an IdentityHashMap.
Map<Age, String> map = new IdentityHashMap<Age, String>();The output is now changed:
One Element added: Size is 1As seen for the IdentityHashMap:
Two Elements added: Size is 2
Three Elements added: Size is 3
Value recovered For 7 is : [ seven Again ]
- Every key used was a different object. Three different memory locations. So all went into the map as three separate enteries.
- The size of three indicates that the equals method is not used here.
- The get call did a memory check and therefore found the second entry.
This class implements the Map interface with a hash table,As it indicates, Identity is used over Object equality.
using reference-equality in place of object-equality when
comparing keys (and values). In other words, in an
IdentityHashMap, two keys k1 and k2 are considered equal
if and only if (k1==k2). (In normal Map implementations
(like HashMap) two keys k1 and k2 are considered equal
if and only if (k1==null ? k2==null : k1.equals(k2)).)
Where would we use this implementation ?
From the java docs:
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.