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).

Click here to view code image

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.

Click here to view code image

@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.

Click here to view code image

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:

Click here to view code image

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.

Click here to view code image

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:

Click here to view code image

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:

Click here to view code image

//  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.

Click here to view code image

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.

Click here to view code image

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:

Click here to view code image

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:

Click here to view code image

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:

Click here to view code image

StringBuilder mutableCourse = unmodMapWithMutableCourses.get(500);
mutableCourse.replace(0, 7, “Smartass”);

Click here to view code image

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.

Click here to view code image

// 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:

Click here to view code image

Original: {500=Smartass Java, 300=Intermediate Java}
Copy: {300=Intermediate Java, 500=Smartass Java, 200=Basic Java,
       400=Advanced Java}

Leave a Reply

Your email address will not be published. Required fields are marked *