1. Overview
It is sometimes preferable to disallow modifications to the java.util.Map such as sharing read-only data across threads. For this purpose, we can use either an Unmodifiable Map or an Immutable Map.
In this quick tutorial, we’ll see what’s the difference between them. Then, we’ll present various ways in which we can create an Immutable Map.
2. Unmodifiable vs Immutable
An Unmodifiable Map is just a wrapper over a modifiable map and it doesn’t allow modifications to it directly:
Map<String, String> mutableMap = new HashMap<>();
mutableMap.put("USA", "North America");
Map<String, String> unmodifiableMap = Collections.unmodifiableMap(mutableMap);
assertThrows(UnsupportedOperationException.class,
() -> unmodifiableMap.put("Canada", "North America"));
But the underlying mutable map can still be changed and the modifications are reflected in the Unmodifiable map as well:
mutableMap.remove("USA");
assertFalse(unmodifiableMap.containsKey("USA"));
mutableMap.put("Mexico", "North America");
assertTrue(unmodifiableMap.containsKey("Mexico"));
An Immutable Map, on the other hand, contains its own private data and doesn’t allow modifications to it. Therefore, the data cannot change in any way once an instance of the Immutable Map is created.
3. Guava’s Immutable Map
Guava provides immutable versions of each java.util.Map using ImmutableMap. It throws an UnsupportedOperationException whenever we try to modify it.
Since it contains its own private data, this data won’t change when the original map is changed.
We’ll now discuss various ways of creating instances of the ImmutableMap.
3.1. Using copyOf() Method
First, let’s use the ImmutableMap.copyOf() method that returns a copy of all the entries as in the original map:
ImmutableMap<String, String> immutableMap = ImmutableMap.copyOf(mutableMap);
assertTrue(immutableMap.containsKey("USA"));
It cannot be modified directly or indirectly:
assertThrows(UnsupportedOperationException.class,
() -> immutableMap.put("Canada", "North America"));
mutableMap.remove("USA");
assertTrue(immutableMap.containsKey("USA"));
mutableMap.put("Mexico", "North America");
assertFalse(immutableMap.containsKey("Mexico"));
3.2. Using builder() Method
We can also use ImmutableMap.builder() method to create a copy of all the entries as in the original map.
Moreover, we can use this method to add additional entries that are not present in the original map:
ImmutableMap<String, String> immutableMap = ImmutableMap.<String, String>builder()
.putAll(mutableMap)
.put("Costa Rica", "North America")
.build();
assertTrue(immutableMap.containsKey("USA"));
assertTrue(immutableMap.containsKey("Costa Rica"));
The same as in the previous example, we cannot modify it directly or indirectly:
assertThrows(UnsupportedOperationException.class,
() -> immutableMap.put("Canada", "North America"));
mutableMap.remove("USA");
assertTrue(immutableMap.containsKey("USA"));
mutableMap.put("Mexico", "North America");
assertFalse(immutableMap.containsKey("Mexico"));
3.3. Using of() Method
Finally, we can use ImmutableMap.of() method to create an immutable map with a set of entries provided on the fly. It supports at most five key/value pairs:
ImmutableMap<String, String> immutableMap
= ImmutableMap.of("USA", "North America", "Costa Rica", "North America");
assertTrue(immutableMap.containsKey("USA"));
assertTrue(immutableMap.containsKey("Costa Rica"));
We cannot modify it as well:
assertThrows(UnsupportedOperationException.class,
() -> immutableMap.put("Canada", "North America"));
3.4. Using ofEntries() Method
Finally, we can use the ImmutableMap.ofEntries() method to create an unmodifiable map containing keys and values extracted from the given entries.
Unlike ImmutableMap.of(), we can pass any number of entries as arguments to this method.
Now, let’s exemplify the use of the ImmutableMap.ofEntries() method:
ImmutableMap<Integer, String> immutableMap
= ImmutableMap.ofEntries(new AbstractMap.SimpleEntry<>(1, "USA"));
assertEquals(1, immutableMap.size());
assertThat(immutableMap, IsMapContaining.hasEntry(1, "USA"));
As we can see, we used the AbstractMap class to create the map entries.
Similarly, trying to modify the returned map will result in throwing UnsupportedOperationException:
ImmutableMap<Integer, String> immutableMap
= ImmutableMap.ofEntries(new AbstractMap.SimpleEntry<>(1, "USA"), new AbstractMap.SimpleEntry<>(2, "Canada"));
assertThrows(UnsupportedOperationException.class, () -> immutableMap.put(2, "Mexico"));
Typically, the method throws IllegalArgumentException if we add an entry with a duplicate key:
assertThrows(IllegalArgumentException.class,
() -> ImmutableMap.ofEntries(new AbstractMap.SimpleEntry<>(1, "USA"), new AbstractMap.SimpleEntry<>(1, "Canada")));
Please note that ImmutableMap.ofEntries() doesn’t accept null as a key or value.
So, attempting to specify an entry with a null key will result in NullPointerException:
assertThrows(NullPointerException.class,
() -> ImmutableMap.ofEntries(new AbstractMap.SimpleEntry<>(null, "USA")));
Silmarily, specifying null as a value will cause NullPointerException as well:
assertThrows(NullPointerException.class,
() -> ImmutableMap.ofEntries(new AbstractMap.SimpleEntry<>(1, null)));
4. Conclusion
In this quick article, we discussed the differences between an Unmodifiable Map and Immutable Map.
We also had a look at different ways of creating Guava’s ImmutableMap.
And, as always, the complete code examples are available over on GitHub.