1. Introduction
In this tutorial, we’re looking into two flavors of collections in addition to the common Collection classes we have in Java. As we know, we have three core collection classes: Map, List, and Set. They have corresponding Unmodifiable and Immutable versions.
In our examples, we cover the Map family of collections in Java. The Collections.unmodifiableMap() and Map.of() methods apply to the Map, whereas the Collections.unmodifiableList(), Collections.unmodifiableSet(), List.of(), and Set.of() are the corresponding utility methods for the List and Set collection classes. The same concepts apply to the List and Set collection classes.
2. Unmodifiable Collections
An unmodifiable collection is a wrapper around a mutable collection that prevents modifications through the wrapper reference. We get the unmodifiable reference by using a utility method, for example, unmodifiableMap() in the case of Java Map collection:
Map<String, String> modifiableMap = new HashMap<>();
modifiableMap.put("name1", "Michael");
modifiableMap.put("name2", "Harry"
Here, modifiableMap is a reference to a map collection. We put two key-value pairs in the map. Next, we get unmodifiableMap using Collection.unmodifiableMap() utility method:
Map<String, String> unmodifiableMap = Collections.unmodifiableMap(modifiableMap);
We get a new reference unmodifiableMap to the underlying collection. This unmodifiable reference is special in the sense that we can’t use it to add or delete entries in the map. But it does not affect the underlying collection or the other reference variable modifiableMap. We can still add more key-value pairs in the collection using the initial modifiableMap reference:
modifiableMap.put("name3", "Micky");
The changes to the collection will reflect through the new reference variable unmodifiableMap as well:
assertEquals(modifiableMap, unmodifiableMap);
assertTrue(unmodifiableMap.containsKey("name3"));
Let’s now try to put an entry using the unmodifiableMap reference variable. It is expected to disallow the operation and will throw an exception:
assertThrows(UnsupportedOperationException.class, () -> unmodifiableMap.put("name3", "Micky"));
The two references modifiableMap and unModifiableMap are pointing to the same map in memory but they behave differently. One can operate on the map freely, whereas the other can’t perform any operation that modifies the collection by adding or deleting items from it.
3. Immutable Collections
Immutable collections remain immutable throughout their lifecycle without any modifiable references to them. Immutable collections solve the problem where we’re able to modify an unmodifiable collection using some other reference. To create Immutable collections in Java, we have the utility methods Map.of() or List.of(). Any new reference we create will always be immutable as well:
@Test
public void givenImmutableMap_WhenPutNewEntry_ThenThrowsUnsupportedOperationException() {
Map<String, String> immutableMap = Map.of("name1", "Michael", "name2", "Harry");
assertThrows(UnsupportedOperationException.class, () -> immutableMap.put("name3", "Micky"));
}
We encounter an UnsupportedOperationException exception as soon as we try to put an entry into the immutableMap. The Map.copyOf() gives back a reference to the underlying map that is immutable as well:
@Test
public void givenImmutableMap_WhenUsecopyOf_ThenExceptionOnPut() {
Map<String, String> immutableMap = Map.of("name1", "Michael", "name2", "Harry");
Map<String, String> copyOfImmutableMap = Map.copyOf(immutableMap);
assertThrows(UnsupportedOperationException.class, () -> copyOfImmutableMap.put("name3", "Micky"));
}
Therefore, if we want to make sure there is no other reference to our collection that can modify it, we have to go with Immutable collections in Java.
4. Immutable and Unmodifiable Collections Considerations
4.1. Thread Safety
The immutable classes are, by principle, thread-safe, as multiple threads can access them simultaneously without fear of changing the underlying collections. Using an immutable collection prevents multiple threads from overwriting state, resulting in a thread-safe design. Thread-safe means there is no requirement for explicit synchronization while using in concurrent environments. This also simplifies concurrent programming by eliminating the need for any locks, etc.
4.2. Performance
The performance of unmodifiable or immutable collections is poor when we compare them to the corresponding mutable collections. To update, we can’t do in-place updates. Instead, we have to create new copies of objects. This adds to the overhead and results in bad performance. Also, they may have higher memory usage compared to mutable counterparts due to the creation of new instances. However, the immutable collections excel in scenarios with frequent reads and infrequent writes.
4.3. Mutable Objects
We have to ensure that mutable objects added to immutable collections are defensively copied, to prevent external modifications. A multithreading context will require thread safety of both the collection and the mutable objects it contains.
5. Conclusion
In this article, we have closely examined the collection classes like Map, List, and Set for their Immutable and Unmodifiable flavors. The unmodifiable collections are suitable when we need a collection to remain unmodified through a specific reference but still want the original collection to be mutable. On the other hand, the immutable Collections are ideal when we want to ensure no modifications to the collection whatsoever, even through any reference. Also, we discussed a few of the common use cases. As always, the source code for this article can be found over on GitHub.