Map Interface Enhancements from Java 8 to Java 10

1. Introduction

In this article, we will explore the methods that were added to the Map interface since Java 8 release. No new methods were added in Java 11 and 12.

2. Content 

Map interface had an addition of 25 new methods in Java 8, 9 and 10 release. These methods actually make our job really easy. Why is that? Because at some point we have implemented these methods where implementation spans over one line. Now we have inbuilt methods in the Map interface so we can easily refactor our code using JDK code.

default void forEach(BiConsumer<? super K, ? super V> action)

default V getOrDefault(Object key, V defaultValue)

default V putIfAbsent(K key, V value)

default void replaceAll(
	BiFunction<? super K, ? super V, ? extends V> function)

default boolean remove(Object key, Object value)

default V replace(K key, V value)

default boolean replace(K key, V oldValue, V newValue)

default V computeIfAbsent(
	K key, 
	Function<? super K, ? extends V> mappingFunction) 

default V computeIfPresent(
	K key, 
	BiFunction<? super K, ? super V, ? extends V> remappingFunction)

default V compute(
	K key, 
	BiFunction<? super K, ? super V, ? extends V> remappingFunction)

default V merge(
	K key, 
	V value, 
	BiFunction<? super V, ? super V, ? extends V> remappingFunction)

static <K, V> Entry<K, V> entry(K k, V v)

static <K, V> Map<K, V> ofEntries(
	Entry<? extends K, ? extends V>... entries)

static <K, V> Map<K, V> copyOf(Map<? extends K, ? extends V> map)

static <K, V> Map<K, V> of()

static <K, V> Map<K, V> of(K k1, V v1)

static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2)

static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3)

static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, 
	K k4, V v4)

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)

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)

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)

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)

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)

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)

3. default void forEach(
BiConsumer<? super K, ? super V> action)

forEach method was added in Java 8.

forEach method accepts BiConsumer interface as a parameter. Why BiConsumer? Because Map accepts a pair as input, i.e. key and value.

accept() method in BiConsumer interface accepts two arguments as input and returns a void. We can use this in logging the details of a Map. Interesting part about this method is the HashMap class figures that iteration itself. We don’t need to iterate. We just need to pass the behavior as a parameter. 

Example: Below is the example of how to use this method.

Map<String, Integer> names = new HashMap<>();
names.put("Bran", 4);
names.put("Jon", 3);

names.forEach((k, v) -> logger.log(Level.INFO, k + " " + v));

To achieve this functionality prior to Java 8, we have to iterate the Map manually. If we iterate the Map manually, that iteration is called an external iteration. Prior to Java 8 you would have written above code like this.

for (Entry<String, Integer> entry : names.entrySet()) {
	logger.log(
			Level.INFO, 
			entry.getKey() + " " + entry.getValue()));
}

4. default V getOrDefault(Object key, V defaultValue)

getOrDefault method was added in Java 8.

getOrDefault() method is an extension of the get() method. How so? get() method returns value of the key, if the key is present. It will return null if the key is not present. get() method will return null if the Map implementation allows null values. So when null is returned it doesn’t mean that value doesn’t exist. 

Example 1: Below example shows that key “Ramsey” is not present in Map so default value -1 is returned.

Map<String, Integer> names = new HashMap<>();
names.put("Bran", 4);
names.put("Jon", 3);
	
Integer value = names.getOrDefault("Ramsey", -1);
Assert.assertEquals(Integer.valueOf(-1), value);

Example 2: Below example shows that the key “Ramsey” is present and its corresponding value i.e. null is returned.

Map<String, Integer> names = new HashMap<>();
names.put("Bran", 4);
names.put("Jon", 3);
names.put("Ramsey", null);

Integer value = names.getOrDefault("Ramsey", -1);
Assert.assertEquals(null, value);

5. default V putIfAbsent(K key, V value)

putIfAbsent method was added in Java 8.

putIfAbsent method has two different usages

  • If the value of the key is null, then specified value is added to map
  • If the key is null, then it inserts both key and value in the map

If you have any code like this, you can effectively replace that code with putIfAbsent.

V v = map.get(key);
if (v == null) {
	v = map.put(key, value);
}
return v;

It just means that if the value or key is null, the entire entry is replaced with a specified key and value. I will explain this using 3 examples. 

Example 1: In this example, we insert a new key and new value into the Map using putIfAbsent.

Map<String, Integer> names = new HashMap<>();
names.put("Bran", 4);
Assert.assertFalse(names.containsKey("Jon"));

Integer oldValue = names.putIfAbsent("Jon", 100_000);
Assert.assertEquals(oldValue, null);

Integer newVal = names.get("Jon");
Assert.assertEquals(Integer.valueOf(100_000), newVal);

Example 2: In this example, the key is present in the Map, but value isn’t. putIfAbsent method will add the specified value along with the key in the Map.

Map<String, Integer> names = new HashMap<>();
names.put("Bran", 4);
names.put("Jon", null);
Assert.assertTrue(names.containsKey("Jon"));

Integer oldValue = names.putIfAbsent("Jon", 100_000);
Assert.assertEquals(null, oldValue);
Assert.assertEquals(Integer.valueOf(100_000), names.get("Jon"));

Example 3: In this example, the key and value both are present in Map already. So calling putIfAbsent method on this Map won’t have any effect.

Map<String, Integer> names = new HashMap<>();
names.put("Bran", 4);
names.put("Jon", 3);
Assert.assertTrue(names.containsKey("Jon"));

Integer oldValue = names.putIfAbsent("Jon", 100_000);
Assert.assertEquals(Integer.valueOf(3), oldValue);
Assert.assertEquals(Integer.valueOf(3), names.get("Jon"));

6. default void replaceAll(
BiFunction<? super K, ? super V, ? extends V> function)

replaceAll method was added in Java 8.

replaceAll method is used to replace every value using specified Function in parameter.

Map<String, Integer> names = new HashMap<>();
names.put("Bran", 4);
names.put("Jon", 3);

names.replaceAll((String k, Integer v) -> v * 5);

names.forEach((k, v) -> System.out.println(k + " " + v));

Input :
Bran 4
Jon 3

Output : 
Bran 20
Jon 15

7. default boolean remove(Object key, Object value)

remove(k, v) method removes the entry from Map if specified key is mapped to this value. 

Example 1: remove method has arguments as “Jon” and 3. This is a perfect match in our input too, hence the call to remove() method will remove this entry.

Map<String, Integer> names = new HashMap<>();
names.put("Bran", 4);
names.put("Jon", 3);

names.remove("Jon", 3);

Assert.assertFalse(names.containsKey("Jon"));

Example 2: remove method has arguments as “Jon” and 123 whereas our input Map contains key “Jon” and value 3. This is not a match and hence Map will be left unchanged.

Map<String, Integer> names = new HashMap<>();
names.put("Bran", 4);
names.put("Jon", 3);

names.remove("Jon", 123);

Assert.assertTrue(names.containsKey("Jon"));

8. default V replace(K key, V value)

replace method was added in Java 8.

replace method is used to replace the value associated with this key if the key exists in Map.

replace method returns old value associated with the key if the key is found. If the key is not present, it returns null. It can also return null if we previously mapped the key to null.

Example 1: In this example, the key exists in Map, so calling the replace method will insert a new value for this key.

Map<String, Integer> names = new HashMap<>();
names.put("Bran", 4);
names.put("Jon", 3);

Integer newValue = Integer.valueOf(100);
Integer oldValue = names.replace("Jon", newValue);

Assert.assertEquals(Integer.valueOf(3), oldValue);

Assert.assertEquals(newValue, names.get("Jon"));

Example 2: In this example, the key does not exist in Map, so calling the replace method will have no effect on this Map.

Map<String, Integer> names = new HashMap<>();
names.put("Bran", 4);
names.put("Jon", null);

Integer oldValue = names.replace("Ramsey", Integer.valueOf(100));
Assert.assertEquals(null, oldValue);
Assert.assertEquals(oldValue, names.get("Ramsey"));

9. default boolean
replace(K key, V oldValue, V newValue)

replace method was added in Java 8.

This replace method is used to replace the value of the key if and only if both key and oldValue match in Map.

This method returns true if the Map is changed because of this call, else it returns false. It means that if the oldValue is replaced with a newValue, then it returns true, else it returns false.

Example 1: In this example, the key and the value are present in the Map. Hence, the replace method would replace the value 3 with value 100. The replace method will return true.

Map<String, Integer> names = new HashMap<>();
names.put("Bran", 4);
names.put("Jon", 3);

boolean isReplaced = names.replace("Jon", 3, 100);
Assert.assertTrue(isReplaced);

Example 2: In this example, the key is present in Map but value is not. Hence, the call to replace method won’t have any change on Map. The replace method will return false.

Map<String, Integer> names = new HashMap<>();
names.put("Bran", 4);
names.put("Jon", 3);

boolean isReplaced = names.replace("Jon", 56, 100);
Assert.assertFalse(isReplaced);

10. default V computeIfAbsent(
K key,
Function<? super K, ? extends V> mappingFunction) 

computeIfAbsent method was added in Java 8.

computeIfAbsent is used to compute the value of the specified key 

  • if the value is null
  • Or the key is not already associated with value

Important part here is that if the mappingFunction produces a null value, then Map is not modified at all. Also, don’t modify Map using mappingFunction. If you are modifying Map using mappingFunction, you are using mappingFunction wrong. Try using the putIfAbsent method.

Let us take an example. Assume that you are given a 2d array with two elements in every row and you are expected to create a Graph out of it. Which means your output would be Map<Integer, Set<Integer>>. Key is Integer means vertex and Set<Integer> means vertex that the current vertex can travel to. Assume that the graph is directed.

Normally, we would write below code to create a graph out of 2d array.

int[][] arr = {{0, 1}, {1, 2}, {3, 4}, {1, 50}};

Map<Integer, Set<Integer>> graph = new HashMap<>();
for (int[] a : arr) {
	if (graph.containsKey(a[0])) {
		graph.get(a[0]).add(a[1]);
	} else {
		graph.put(a[0], new HashSet<>());
		graph.get(a[0]).add(a[1]);
	}
}

If we can achieve same using computeIfAbsent as below :

Map<Integer, Set<Integer>> graph_compute = new HashMap<>();

for (int[] a : arr) {
	// if the key exists then nothing is done. old value is  
	// returned. else new value is computed and 
	// inserted in the map.
	graph_compute
		.computeIfAbsent(
			a[0], 
			k -> new HashSet<>())
		.add(a[1]);
}

11. default V computeIfPresent(
K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction)

computeIfPresent method was added in Java 8.

computeIfPresent method is used to compute the new mapping given that the key specified in the parameter is present in Map and also value is not null.

It is paramount to note that if the remappingFunction computes and yields null, then the mapping is removed from Map.

Also, the remappingFunction must not try to modify the Map.

Example 1 : Get the frequency count of words. Using plain old Map method, we can achieve this using below code.

// words is List<String>
for(String str : words) {
   if(!frequency.containsKey(str)) {
       frequency.put(str, 0);
   }
   frequency.put(str, frequency.get(str) + 1);
}

Same can be achieved using computeIfPresent

for(String str : words) {
   if(!frequency.containsKey(str)) {
       frequency.put(str, 0);
   }
   frequency.computeIfPresent(str, (k, v) -> v + 1);
}

Using computeIfPresent method is a better alternative because it allows us to concentrate on our use case.

Using frequency.put(str, frequency.get(str) + 1); is not bad too but you are more focused on getting value from Map. More focus should be on our business logic, i.e. counting frequency of word.

12. default V compute(
K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction)

compute method was added in Java 8.

compute() method is used to compute a mapping for the specified key and its value using remappingFunction. remappingFunction is a BiFunction which accepts key and value as input and returns new value.

Example 1 : Key exists in Map and we are providing remappingFunction to compute the new value of this key.

Map<String, Integer> names = new HashMap<>();
names.put("Bran", 4);
names.put("Jon", 3);
								//3          3     4
names.compute("Jon", (k, v) -> k.length() + v + "Snow".length());

Assert.assertEquals(Integer.valueOf(10), names.get("Jon"));

What happens if the key in the parameter doesn’t exist in Map? 

  • If remappingFunction returns null, then the compute method returns null and won’t change Map.
  • If remappingFunction returns with a non-null value, then the key and new value is added to Map.

Example 2: Key is removed from Map as the remappingFunction returns null.

Map<String, Integer> names = new HashMap<>();
names.put("Bran", 4);
names.put("Jon", 3);
names.compute("Jon", (k, v) -> null);

Assert.assertFalse(names.containsKey("Jon"));

Example 3: It created new mapping as part of the call to compute method.

Map<String, Integer> names = new HashMap<>();
names.put("Bran", 4);
names.put("Jon", 3);
names.compute("Ramsey", (k, v) -> "Snow".length());

Assert.assertEquals(Integer.valueOf(4), names.get("Ramsey"));

13. default V merge(
K key,
V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction)

merge method was added in Java 8.

merge() method is like compute method. It is used to merge the specified value with the value already mapped to the specified key in the parameter.

Closely look at remappingFunction. It is a BiFunction that accepts V as two inputs and returns a V. The first and second parameter, i.e. V, corresponds to the value of the specified key and value parameter in method. So computation of new value is done using old value and value specified in the parameter.

Example 1: Merge two values into one. Key already exists in Map.

Map<String, Integer> names = new HashMap<>();
names.put("Bran", 4);
names.put("Jon", 3);

names.merge("Bran", 10, (v1, v2) -> v1 + v2);
Assert.assertEquals(Integer.valueOf(4 + 10), names.get("Bran"));

If the remappingFunction returns null 

  • This method will remove the specified key from Map. 
  • If a key was not present in Map, then there is no effect on Map.

Example 2 : remappingFunction returns null, hence the mapping would be removed from Map.

Map<String, Integer> names = new HashMap<>();
names.put("Bran", 4);
names.put("Jon", 3);

names.merge("Bran", 1, (v1, v2) -> null);
Assert.assertFalse(names.containsKey("Bran"));

Example 3: key does not exist in Map. call to merge method creates new mapping in Map.

Map<String, Integer> names = new HashMap<>();
names.put("Bran", 4);
names.put("Jon", 3);

names.merge("Ramsey", 1, (v1, v2) -> v2);
Assert.assertTrue(names.containsKey("Ramsey"));

14. Static factories

In previous articles on List and Set interface enhancement, we saw the static factories added to List and Set interfaces. Being said that, several static factory methods were added to the Map interface too.

Three major static factories were in Java 9 and Java 10.

The returned instances are immutable and cannot be changed structurally. Custom implementation is done for the returned Map object. It is not HashMap. Look at the java.util.ImmutableCollections class code, it is fantastic.

Null elements are not allowed in a static factory. If you try to add a null element in the static factory method’s parameter, it will cause NullPointerException. This is a tremendous improvement.

14.1 static <K, V> Entry<K, V> entry(K k, V v)

entry method was added in Java 9.

entry method returns an immutable container that holds non-null key value. 

Entry<String, Integer> entry = Map.entry("Jon", 3);

14.2 static <K, V> Map<K, V>
ofEntries(Entry<? extends K, ? extends V>… entries)

ofEntry static factory was added in Java 9

Map<String, String> starks = Map.ofEntries(
Map.entry("Jon", "Targaryen"), Map.entry("Arya", "Stark"),
       Map.entry("Sansa", "Stark"),
       Map.entry("Bran", "Stark"),
       Map.entry("Rickon", "Stark"));

14.3  static <K, V> Map<K, V>
copyOf(Map<? extends K, ? extends V> map)

copyOf method was in Java 10.

copyOf method was added in Java 10. copyOf method returns the unmodifiable map, which comprises elements from a given Map. While the elements are copied over to the new Map, the iteration order of the specified Map is maintained.

Map<String, Integer> map = new HashMap<>();
map.put("Bran", 4);
map.put("Jon", 3);

Map<String, Integer> names = Map.copyOf(map);

14.4 of static factory

of() static factories were added in Java 8. 

MethodWhat does it do?
static <K, V> Map<K, V> of()Returns unmodifiable empty Map
static <K, V> Map<K, V> of(K k1, V v1)Returns an unmodifiable Map with one entry
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2)Returns an unmodifiable Map with two entries
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3)Returns an unmodifiable Map with three entries
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4)Returns an unmodifiable Map with four entries
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)Returns an unmodifiable Map with five entries
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)Returns an unmodifiable Map with six entries
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)Returns an unmodifiable Map with seven entries
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)Returns an unmodifiable Map with eight entries
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)Returns an unmodifiable Map with nine entries
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)Returns an unmodifiable Map with 10 entries
Map.of();

Map.of("Jon", "Targaryen");

Map.of("Jon", "Targaryen", "Arya", "Stark");

Map.of("Jon", "Targaryen", "Arya", "Stark", "Sansa", "Stark");

Map.of("Jon", "Targaryen", "Arya", "Stark", "Sansa", "Stark", 
		"Bran", "Stark");

Map.of("Jon", "Targaryen", "Arya", "Stark", "Sansa", "Stark", 
		"Bran", "Stark", "Rickon", "Stark");

Map.of("Jon", "Targaryen", "Arya", "Stark", "Sansa", "Stark", 
		"Bran", "Stark", "Rickon", "Stark", "Rickard", "Stark");

Map.of("Jon", "Targaryen", "Arya", "Stark", "Sansa", "Stark", 
		"Bran", "Stark", "Rickon", "Stark", "Rickard", "Stark", 
		"Lyarra", "Stark");

Map.of("Jon", "Targaryen", "Arya", "Stark", "Sansa", "Stark", 
		"Bran", "Stark", "Rickon", "Stark", "Rickard", "Stark", 
		"Lyarra", "Stark", "Benjen", "Stark");

Map.of("Jon", "Targaryen", "Arya", "Stark", "Sansa", "Stark", 
		"Bran", "Stark", "Rickon", "Stark", "Rickard", "Stark", 
		"Lyarra", "Stark", "Benjen", "Stark", "Catelyn", "Stark");

Map.of("Jon", "Targaryen", "Arya", "Stark", "Sansa", "Stark", 
		"Bran", "Stark", "Rickon", "Stark", "Rickard", "Stark", 
		"Lyarra", "Stark", "Benjen", "Stark", "Catelyn", "Stark", 
		"Eddard", "Stark");

15. Conclusion

That is all on Map interface enhancements. All the methods that we saw in this article are extremely useful as we remove a lot of boilerplate code that we write to achieve the same functionality.

Leave a Reply

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