In Java, a HashMap typically uses the .equals() method to compare keys, meaning that two keys are considered equal if their contents are the same.
However, if you want to compare keys by reference (i.e., you want two keys to be considered equal only if they refer to the same object in memory), you can use an IdentityHashMap.
An IdentityHashMap uses reference equality (==) instead of value equality (equals()). This means that it only considers two keys equal if they are the same instance (i.e., they point to the same memory location).
Key Characteristics of IdentityHashMap
Reference Equality: Compares keys using == instead of .equals().
Unusual Behavior for Autoboxing: When using primitive types like int with IdentityHashMap, autoboxing may lead to unexpected results since different instances of Integer objects are not guaranteed to be equal under ==.
Performance Considerations: Since IdentityHashMap does not rely on calculating hash codes from the keyโs value, its performance characteristics may differ slightly from HashMap.
Importing IdentityHashMap
To use IdentityHashMap, you must import it from the java.util package.
import java.util.IdentityHashMap;
Example 1: Basic Usage of IdentityHashMap
Letโs begin with a simple example to show how IdentityHashMap compares keys using reference equality:
import java.util.IdentityHashMap; public class IdentityHashMapExample { public static void main(String[] args) { IdentityHashMap<String, String> map = new IdentityHashMap<>(); String key1 = new String("key"); String key2 = new String("key"); // Another String object with the same content as key1 map.put(key1, "value1"); map.put(key2, "value2"); // This will be treated as a different key System.out.println("Size of map: " + map.size()); // Output: 2 System.out.println("Value for key1: " + map.get(key1)); // Output: value1 System.out.println("Value for key2: " + map.get(key2)); // Output: value2 } }
Explanation:
Here, key1 and key2 are different String objects with the same content (“key”), but IdentityHashMap treats them as different keys because it compares the references using ==.
Therefore, both entries are stored, and the map has a size of 2.
Example 2: IdentityHashMap with Integer Autoboxing
Hereโs an example demonstrating the behavior of IdentityHashMap when used with autoboxed Integer objects:
import java.util.IdentityHashMap; public class IdentityHashMapAutoboxing { public static void main(String[] args) { IdentityHashMap<Integer, String> map = new IdentityHashMap<>(); Integer key1 = 1000; Integer key2 = 1000; // Autoboxing: different instances with the same value Integer key3 = 10; Integer key4 = 10; // Autoboxing: cached Integer instances for small numbers map.put(key1, "value1"); map.put(key2, "value2"); // Different instance, so this will be treated as a different key map.put(key3, "value3"); map.put(key4, "value4"); // Same instance, will overwrite the previous value System.out.println("Size of map: " + map.size()); // Output: 3 System.out.println("Value for key1: " + map.get(key1)); // Output: value1 System.out.println("Value for key2: " + map.get(key2)); // Output: value2 System.out.println("Value for key3: " + map.get(key3)); // Output: value4 System.out.println("Value for key4: " + map.get(key4)); // Output: value4 } }
Explanation:
When Integer objects are created using autoboxing, Java caches small numbers (between -128 and 127), so key3 and key4 refer to the same object.
For larger numbers, such as 1000, new instances are created for each autoboxing operation, so key1 and key2 are different objects even though they have the same value. This causes IdentityHashMap to store them as separate keys.
Example 3: Using Custom Objects as Keys
Letโs see how IdentityHashMap behaves when using custom objects as keys:
import java.util.IdentityHashMap; class Person { String name; Person(String name) { this.name = name; } @Override public String toString() { return name; } } public class IdentityHashMapCustomObject { public static void main(String[] args) { IdentityHashMap<Person, String> map = new IdentityHashMap<>(); Person p1 = new Person("Alice"); Person p2 = new Person("Alice"); // Different instance with the same name map.put(p1, "Developer"); map.put(p2, "Designer"); // Different instance, treated as a separate key System.out.println("Size of map: " + map.size()); // Output: 2 System.out.println("p1's profession: " + map.get(p1)); // Output: Developer System.out.println("p2's profession: " + map.get(p2)); // Output: Designer } }
Explanation:
Even though p1 and p2 have the same value for the name attribute, they are treated as separate keys because IdentityHashMap compares their references using ==.
Hence, the map stores two separate entries, and both objects (p1 and p2) are treated as distinct keys.
Example 4: Comparing with HashMap
Letโs contrast the behavior of IdentityHashMap with HashMap:
import java.util.HashMap; import java.util.IdentityHashMap; public class CompareMaps { public static void main(String[] args) { // Using HashMap (compares keys with .equals()) HashMap<String, String> hashMap = new HashMap<>(); String key1 = new String("key"); String key2 = new String("key"); hashMap.put(key1, "value1"); hashMap.put(key2, "value2"); // Will overwrite the value of key1 because .equals() returns true System.out.println("HashMap size: " + hashMap.size()); // Output: 1 System.out.println("HashMap value for key1: " + hashMap.get(key1)); // Output: value2 // Using IdentityHashMap (compares keys with ==) IdentityHashMap<String, String> identityHashMap = new IdentityHashMap<>(); identityHashMap.put(key1, "value1"); identityHashMap.put(key2, "value2"); // Will not overwrite because key1 and key2 are different objects System.out.println("IdentityHashMap size: " + identityHashMap.size()); // Output: 2 System.out.println("IdentityHashMap value for key1: " + identityHashMap.get(key1)); // Output: value1 System.out.println("IdentityHashMap value for key2: " + identityHashMap.get(key2)); // Output: value2 } }
Explanation:
In the HashMap example, since key1 and key2 are considered equal (.equals() returns true), the second put operation overwrites the value.
In the IdentityHashMap example, the two keys are not equal (key1 == key2 is false), so both entries are stored separately.
When to Use IdentityHashMap
Object Reference Uniqueness: If you need to distinguish objects based on their references rather than their content, IdentityHashMap is useful.
Performance for Caching: In certain caching scenarios where keys must be compared by reference, IdentityHashMap can be more efficient.
Conclusion
IdentityHashMap is a specialized type of map that compares keys by reference (==). It differs from the typical HashMap, which compares keys by value (.equals()). While IdentityHashMap is not commonly used, it can be useful in scenarios where reference equality is required. Understanding its behavior with different data types, such as autoboxed primitives and custom objects, helps to avoid unintended consequences.