Creating Unmodifiable Maps – Collections: Part II
By Jaime Williams / August 23, 2022 / No Comments / Executing Stream Pipelines, Oracle Certification Exam
Creating Unmodifiable Maps
Creating unmodifiable maps is analogous to creating unmodifiable lists (§12.2, p. 649) or unmodifiable sets (p. 804). The Map<K, V> interface provides factory methods to create unmodifiable maps that have the following characteristics:
- Keys and values in such maps cannot be added, removed, or updated. Any such attempt will result in an UnsupportedOperationException to be thrown. However, if the keys and values themselves are mutable, the map may appear to be modified.
- The null value cannot be used for keys and values, and will result in a Null-PointerException if an attempt is made to create such a map with null keys or null values.
- Duplicate keys are rejected when creating such a map, resulting in an Illegal-ArgumentException.
- The iteration order of mappings in such maps is unspecified.
- Such maps are serializable if their keys and values are serializable (§20.5, p. 1261).
static <K,?V> Map<K,?V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4,
K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8,
K k9, V v9, K k10, V v10)
This method is overloaded, accepting any number of entries (k, v) from 0 to 10. It returns an unmodifiable map containing the number of mappings specified. It throws a NullPointerException if any key or value is null. It throws an IllegalArgumentException if there are any duplicate keys.
@SafeVarargs static <K,V> Map<K,V> ofEntries(
Map.Entry<? extends K,? extends V>… entries)
This variable arity method returns an unmodifiable map containing an arbitrary number of entries. It throws a NullPointerException if a key or a value is null, or if the variable arity parameter entries is null. The annotation suppresses the heap pollution warning in its declaration and unchecked generic array creation warning at the call sites. See the method entry() below to create individual entries.
static <K,V> Map.Entry<K,V> entry(K k, V v)
This generic method returns an unmodifiable Map.Entry object containing the specified key and value. Attempts to create an entry using a null key or a null value result in a NullPointerException.
Each entry—that is, <key, value> pair—is represented by an object implementing the nested Map.Entry<K, V> interface. An entry can be manipulated by methods defined in this interface, which are self-explanatory:
interface Entry<K, V> { // Nested interface in the Map<K, V> interface.
K getKey();
V getValue();
V setValue(V value); // Only if the entry is modifiable.
}
static <K,V> Map<K,V> copyOf(Map<? extends K, ? extends V> map)
This generic method returns an unmodifiable map containing copies of the entries in the specified map. The specified map must not be null, and it must not contain any null keys or values—otherwise, a NullPointerException is thrown. If the specified map is subsequently modified, the returned Map<K,V> will not reflect such modifications.
The code below shows that a map created by the Map.of() method cannot be modified. We cannot change or remove any entry in the map. Note that the Map.of() method allows up to 10 entries, and there is no variable arity Map.of() method. The map returned is also not an instance of the HashMap class.
Map<Integer, String> jCourses = Map.of(
200, “Basic Java”, 300, “Intermediate Java”,
400, “Advanced Java”, 500, “Kickass Java”);
// jCourses.put(200, “Java Jive”); // UnsupportedOperationException
// jCourses.remove(500); // UnsupportedOperationException
System.out.println(jCourses instanceof HashMap); // false
System.out.println(jCourses);
// {200=Basic Java, 400=Advanced Java, 300=Intermediate Java, 500=Kickass Java}
The Map.of() method does not allow duplicate keys, and keys or values cannot be null:
Map<Integer, String> coursesMap1
= Map.of(101, “Java 1.1”, 101, “Java 17”); // IllegalArgumentException
Map<Integer, String> coursesMap2
= Map.of(101, “Java 1.1”, 101, null); // NullPointerException
The following code creates unmodifiable map entries, where both the key and the value are immutable:
// Map.Entry<Integer, String> e0 = Map.entry(100, null); // NullPointerException
Map.Entry<Integer, String> e1 = Map.entry(200, “Basic Java”);
Map.Entry<Integer, String> e2 = Map.entry(300, “Intermediate Java”);
Map.Entry<Integer, String> e3 = Map.entry(400, “Advanced Java”);
Map.Entry<Integer, String> e4 = Map.entry(500, “Kickass Java”);
The variable arity Map.ofEntries() method can be used to create an unmodifiable map from an arbitrary number of unmodifiable entries.
Map<Integer, String> unmodCourseMap = Map.ofEntries(e1, e2, e3, e4); // Varargs
// {300=Intermediate Java, 500=Kickass Java, 200=Basic Java, 400=Advanced Java}
//unmodCourseMap.replace(200, “Java Jive”); // UnsupportedOperationException
//unmodCourseMap.remove(500); // UnsupportedOperationException
The following code creates mutable course names that we will use in the next map.
StringBuilder mc1 = new StringBuilder(“Basic Java”);
StringBuilder mc2 = new StringBuilder(“Intermediate Java”);
StringBuilder mc3 = new StringBuilder(“Advanced Java”);
StringBuilder mc4 = new StringBuilder(“Kickass Java”);
The following code creates unmodifiable map entries, where the keys are immutable but the values are mutable:
Map.Entry<Integer, StringBuilder> me1 = Map.entry(200, mc1);
Map.Entry<Integer, StringBuilder> me2 = Map.entry(300, mc2);
Map.Entry<Integer, StringBuilder> me3 = Map.entry(400, mc3);
Map.Entry<Integer, StringBuilder> me4 = Map.entry(500, mc4);
We can use the variable arity Map.ofEntries() method to create an unmodifiable map, where trying to replace or remove a course results in an UnsupportedOperation-Exception:
Map<Integer, StringBuilder> unmodMapWithMutableCourses
= Map.ofEntries(me1, me2, me3, me4); // Varargs
System.out.println(unmodMapWithMutableCourses);
// {200=Basic Java, 500=Kickass Java, 300=Intermediate Java, 400=Advanced Java}
// unmodMapWithMutableCourses.replace(200, mc4); // UnsupportedOperationException
// unmodMapWithMutableCourses.remove(400); // UnsupportedOperationException
However, the mutable values in the map can be modified:
StringBuilder mutableCourse = unmodMapWithMutableCourses.get(500);
mutableCourse.replace(0, 7, “Smartass”);
System.out.println(unmodMapWithMutableCourses);
// {400=Advanced Java, 500=Smartass Java, 200=Basic Java, 300=Intermediate Java}
The code below shows how we can make a copy of a map. The Map.copyOf() method creates a copy of the map passed as an argument at (1). The map created is unmodifiable analogous to the maps created with the Map.of() or Map.ofEntries() methods. The code also shows that modifying the original map does not reflect in the copy of the map.
// Original map:
Map<Integer, StringBuilder> courseMap = new HashMap<>();
courseMap.put(200, mc1); courseMap.put(300, mc2);
courseMap.put(400, mc3); courseMap.put(500, mc4);
// Unmodifiable copy of the map:
Map<Integer, StringBuilder> copyCourseMap = Map.copyOf(courseMap); // (1)
// Modify original map:
courseMap.remove(200);
courseMap.remove(400);
System.out.println(“Original: ” + courseMap);
System.out.println(“Copy: ” + copyCourseMap);
The code above prints the contents of the maps, showing that the copy of the map was not modified:
Original: {500=Smartass Java, 300=Intermediate Java}
Copy: {300=Intermediate Java, 500=Smartass Java, 200=Basic Java,
400=Advanced Java}