Immutable Map in Java (using Guava and Java 9)
This post will discuss various methods to create an immutable map in Java.
Unmodifiable Maps are “read-only” wrappers over other collections. They do not support any modification operations such as add, remove, and clear, but their underlying collection can be changed. Maps that additionally guarantee that no change in the Collection object will ever be visible are referred to as immutable map.
When you don’t expect to modify a collection, it’s a good practice to defensively copy it into an immutable collection. Immutable maps have many advantages over their mutable siblings, like they are thread-safe, more memory-efficient, and can be passed to untrusted libraries without any side effects.
1. Using Guava Library
The Java Collections framework provides the unmodifiableMap() method, but it is unsafe to use as the returned map is only truly immutable if nobody holds a reference to the original collection. The returned map is also inefficient as the data structures will still have all the overhead of mutable collections, including concurrent modification checks, extra space in hash tables, etc.
Guava provides simple, easy-to-use immutable versions of each map using the ImmutableMap class. Unlike Collections’ unmodifiableMap(), an instance of ImmutableMap contains its own private data and will never change. We can create an ImmutableMap collection in several ways:
⮚ Using the copyOf() method
ImmutableMap.copyOf returns an immutable map containing the same entries as a specified map. It returns a NullPointerException if any key or value in the map is null.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import com.google.common.collect.ImmutableMap; import java.util.HashMap; import java.util.Arrays; import java.util.Map; class Main { // Immutable map in Java public static void main(String[] args) { Map<String, String> mutableMap = new HashMap<>(); mutableMap.put("United States", "Washington D.C."); // ImmutableMap.copyOf() creates a copy of the mutable map ImmutableMap<String, String> immutableMap = ImmutableMap .copyOf(mutableMap); try { // any attempt to modify the map will result in // an `UnsupportedOperationException` immutableMap.put("United Kingdom", "London"); } catch (UnsupportedOperationException ex) { System.out.println("java.lang.UnsupportedOperationException"); } // any changes made to the original map will not be reflected // in the immutable map mutableMap.put("India", "New Delhi"); System.out.println(immutableMap); } } |
Output:
java.lang.UnsupportedOperationException
{United States=Washington D.C.}
⮚ Using a Builder
Guava also provides a builder for creating immutable map instances, as shown below:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import com.google.common.collect.ImmutableMap; import java.util.HashMap; import java.util.Map; class Main { // Immutable map in Java public static void main(String[] args) { Map<String, String> mutableMap = new HashMap<>(); mutableMap.put("United States", "Washington D.C."); // 1. get a builder for creating immutable map instances // 2. `putAll()` adds all entries of the mutable map to the `ImmutableMap` // 3. `put()` associates key with value in the built map // 4. `build()` returns a newly created `ImmutableMap` ImmutableMap<String, String> immutableMap = ImmutableMap.<String, String>builder() .putAll(mutableMap) .put("China", "Beijing") .build(); try { // any attempt to modify the map will result in // an `UnsupportedOperationException` immutableMap.put("United Kingdom", "London"); } catch (UnsupportedOperationException ex) { System.out.println("java.lang.UnsupportedOperationException"); } // any changes made to the original map will not be reflected // in the immutable map mutableMap.put("India", "New Delhi"); System.out.println(immutableMap); } } |
Output:
java.lang.UnsupportedOperationException
{United States=Washington D.C., China=Beijing}
⮚ Using the of() method
ImmutableMap.of() returns an immutable map containing the given entries in order. It throws an IllegalArgumentException if duplicate keys are provided. This works for up to 5 key/value pairs. To create a map with an arbitrary number of entries, we can use Builder’s put() method, which takes key-value pairs instead of a map.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import com.google.common.collect.ImmutableMap; class Main { public static void main(String[] args) { // create an immutable map containing the given entries, in order ImmutableMap<String, String> immutableMap = null; immutableMap = ImmutableMap.of("United States", "Washington D.C."); try { // any attempt to modify the map will result in // an `UnsupportedOperationException` immutableMap.put("United Kingdom", "London"); } catch (UnsupportedOperationException ex) { System.out.println("java.lang.UnsupportedOperationException"); } System.out.println(immutableMap); } } |
Output:
java.lang.UnsupportedOperationException
{United States=Washington D.C.}
2. Collection factory methods (Java 9 and above)
Several collection factory methods have been added in Java 9 that creates a compact, structurally immutable instance of Map. For example,
|
1 2 3 4 5 |
Map.of() // creates an empty map Map.of(k1, v1) // creates a singleton map Map.of(k1, v1, k2, v2) Map.of(k1, v1, k2, v2, k3, v3) … |
This supports maps up to 10 key-value pairs. There is a no var-args overload of Map.of(), which can handle any number of mappings. To create a map with an arbitrary number of entries, we can use
|
1 |
Map.ofEntries(Map.Entry<K,V>…) |
It includes varargs overloads, so there is no fixed limit on the map size. This approach requires each key-value pair to be boxed. For boxing keys and values, we can use
|
1 |
Map.Entry<K,V> entry(K k, V v) |
Here’s complete usage of this method:
|
1 2 3 4 5 6 7 |
Map.ofEntries( entry(k1, v1), entry(k2, v2), entry(k3, v3), // … entry(kn, vn) ); |
As the map is structurally immutable, keys and values cannot be added, removed, or updated from it, but if the contained keys or values are themselves mutable, this may cause the map to behave inconsistently or its contents to appear to change.
That’s all about immutable Map in Java.
Thanks for reading.
To share your code in the comments, please use our online compiler that supports C, C++, Java, Python, JavaScript, C#, PHP, and many more popular programming languages.
Like us? Refer us to your friends and support our growth. Happy coding :)