Using IdentityHashMap in Java tutorial

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.

Related posts

Java Interrupting a Thread Tutorial with Examples

Reentrant Monitors in Java tutorial with code examples

Joining Threads in Java tutorial with code examples