Java 8 – Map’s computeIfAbsent, computeIfPresent, getOrDefault methods tutorial with examples

This is the last article, of a 4 part article series, covering the significant enhancements in Java 8 Collections API. While the 1st part of the series explained the new default methods introduced in Iterable and Iterator interfaces in Java 8(read 1st partRead Part 1-Iterable.forEach,Iterator.remove methods tutorial), the 2nd part covered the new default method removeIf() introduced in Collection interface, (read 2nd partRead Part 2- Collection.removeIf method tutorial), and the 3rd explained List interface’s new sort() and replaceAll() methods(read 3rd partRead Part 3-List.sort & List.replaceAll methods tutorial).

In this tutorial, we will be looking at the enhancements introduced in java.util.Map interface in Java 8. We will first quickly understand what are multi-value maps. Next we will create a multi-value map which will serve as the base problem set for explaining the new Map methods. We will first see the working of Java 8’s new Map.forEach() and Map.replaceAll() methods. Next, we will understand the new default methods introduced in Java 8 which simplify using multi-value maps. These methods are Map.computeIfAbsent(), Map.computeIfPresent() and Map.getOrDefault() methods.

What is a multi-value map
A multi-value map is a normal instance of java.util.Map. The only difference is that instead of having a ‘single’ value corresponding to each key, a multi-value map instead has a ‘collection’ such as a List or a Set as the value stored against each key.

Multi-value map with ArrayList as value
A good example of a use-case for a multi-value map would be of the hash table used to store hash keys and corresponding values. In a hash table, there exists a mappping between a hash value(key) and entries stored in a bucket (value) stored corresponding to it. A hash table is thus essentially a multi-value map.

Having understood the structure of a multi-value map, let us now define the base data set for showing the working of the new Map methods –

Defining the data set/multi-value Map for this tutorial

Java 8 code defining a multi-value map
package com.javabrahman.java8;
public class Employee {
  private String name;
  private Integer age;
  private Double salary;
  public Employee(String name, Integer age, Double salary) {
    this.name = name;
    this.age = age;
    this.salary = salary;
  }
  public String toString(){
    DecimalFormat dformat = new DecimalFormat(".##");
    return "Employee Name:"+this.name
        +"  Age:"+this.age
        +"  Salary:"+dformat.format(this.salary);
  }
//getters and setters for name, age and salary go here
//standard equals() and hashcode() code go here
}
//MultiValueMapsExample.java
package com.javabrahman.java8.collections;
import com.javabrahman.java8.Employee;
import java.util.*;
public class MultiValueMapsExample {
  static Map<Integer, List<Employee>> employeeDOJMap = new HashMap<>();
  
  public static void main(String args[]) {

    List<Employee> list2014 = Arrays.asList(
        new Employee("Deborah Sprightly", 29, 9000.00));
    employeeDOJMap.put(2014, list2014);
    List<Employee> list2015 = Arrays.asList(
        new Employee("Tom Jones", 45, 7000.00),
        new Employee("Harry Major", 25, 10000.00));
    employeeDOJMap.put(2015, list2015);
    List<Employee> list2016 = Arrays.asList(
        new Employee("Ethan Hardy", 65, 8000.00),
        new Employee("Nancy Smith", 22, 12000.00));
    employeeDOJMap.put(2016, list2016);
  }
}

Explanation of the code

  • Employee class is the POJO for employees. It contains three attributes of an employee – name, age and salary.
  • employeeDOJMap is the multi-value map of type Map<Integer, List<Employee>>.
  • employeeDOJMap’s Integer key contains the year of joining of employees as the key, while the List<Employee>> stored as value for each key contains the instances of Employee objects who joined in that year.

Important Note – In the next sections of this tutorial we will be using the base data set defined in the multi-value map named employeeDOJMap. Only the delta/new code in further sections for brevity.

Java 8’s new Map.forEach() and Map.replaceAll() methods
Let us start by quickly going through the relatively simpler methods introduced in Map interface in Java 8- Map.forEach() and Map.replaceAll().

Map.forEach()
Map.forEach() method is defined as –

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

Where,
     – action is the only parameter and is an instance of a BiConsumer functional interface, and,
     – method applies the logic provided via action to all the entries in the map as it ‘consumes’ them.

What is a BiConsumer What is a BiConsumer
java.util.function.BiConsumer is a functional interface, and is a two-arity specialization of a Consumer Functional InterfaceClick to read detailed tutorial on Consumer Functional Interfaces. I.e. it accepts two inputs as arguments and does not return any output.

Map.replaceAll()
Map.replaceAll() method is defined as –

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

Where,
     – function is the only parameter and is an instance of a BiFunction Functional Interface, and,
     – the method applies the logic provided via function to all the values in the map and transforms them inside the map itself.

What is a BiFunction What is a BiFunction
java.util.function.BiFunction is a functional interface, and is a two-arity specialization of FunctionClick to read detailed tutorial on Java 8’s Function Interface. I.e. it accepts two inputs as arguments and returns a result after performing a computation with the input.

Let us now see the Map.forEach() and Map.replaceAll() methods in action –

Java 8 code showing Map.forEach() and Map.replaceAll() methods
System.out.println("Using Map.forEach to print the Employee in employeeDOJMap multi-value map\n");
employeeDOJMap.forEach((year,empList)->System.out.println(year+"-->" +empList));

System.out.println("\nCAPITALIZED Employee Names using Map.replaceAll()");
employeeDOJMap.replaceAll((year, empList) -> {
empList.replaceAll(emp -> {
    emp.setName(emp.getName().toUpperCase());
    return emp;
  });
  return empList;
});
employeeDOJMap.forEach((year, empList)-> System.out.println(year+"-->"+empList));
 OUTPUT of the above code
Using Map.forEach to print the Employee in employeeDOJMap multi-value map –
2016–>[Employee Name: Ethan Hardy  Age: 65  Salary: 8000.0, Employee Name: Nancy Smith  Age: 22  Salary: 12000.0]
2014–>[Employee Name: Deborah Sprightly  Age: 29  Salary: 9000.0]
2015–>[Employee Name: Tom Jones  Age: 45  Salary: 7000.0, Employee Name: Harry Major  Age:25  Salary: 10000.0]
CAPITALIZED Employee Names using Map.replaceAll()
2016–>[Employee Name: ETHAN HARDY  Age: 65  Salary: 8000.0, Employee Name: Nancy Smith  Age: 22  Salary: 12000.0]
2014–>[Employee Name: DEBORAH SPRIGHTLY  Age: 29  Salary: 9000.0]
2015–>[Employee Name: TOM JONES  Age: 45  Salary: 7000.0, Employee Name: Harry Major  Age:25  Salary: 10000.0]
Explanation of the code

  • Map.forEach() method is passed the lambda expressionRead Java 8 Lambda Expressions Tutorial equivalent to a BiConsumer implementation which prints the keys(years) and the corresponding values(employee lists) stored in the employeeDOJMap.
  • Map.replaceAll() method uses a lambda equivalent of a BiFunction implementation. This lambda uses a List.replaceAll() method to replace all the employee names stored inside each of the List<Employee>, with their capitalized versions.

Java 8’s Map.computeIfAbsent() method
As we learnt earlier, a multi-value map stores a collection of values for each key. Lets say we are adding a [key,value] entry to a multi-value map and the key we are adding is not present in the map. This would require for us to check for this probability before insertion and if the key is not present then we will have to instantiate a fresh collection instance as the value for this new key. Only then can we store the value against the key. Also, this check will need to be performed on every insert that we do.

Map.computeIfAbsent() takes away exactly this overhead of writing the multiple line code for such a check by squeezing it into a simple one line code. Map.computeIfAbsent() method is defined as –

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

Where,
     – key is the first parameter which is the key of the multi-value map.
     – function is an instance of java.util.function.Function. It computes and returns the value which is to be used when the key is new i.e. does not have a collection instantiated in the case of a multi-value map.

Have a look at the code sample below to understand the difference in code when using Java 7 versus Java 8’s computeIfAbsent() method. The red code is Java 7 way of checking and instantiating before all insertions. The green code is Java 8 way of doing the same thing using Map.computeIfAbsent() method.

Java 8 code to show usage of Map.computeIfAbsent() method
System.out.println("\nJava 7 way of adding a new key(2017) in a multi-value map\n");
List empList2017 = employeeDOJMap.get(2017);
if (empList2017 == null) {
  empList2017 = new ArrayList<>();
}
empList2017.add(new Employee("Tom Newman", 45, 12000.00));
employeeDOJMap.put(2017, empList2017);
employeeDOJMap.forEach((year,empList)-> System.out.println(year+"-->"+empList));
System.out.println("\nUsing Map.computeIfAbsent() to add a new key(2018) in a multi-value map\n");
employeeDOJMap.computeIfAbsent(2018,empList -> new ArrayList<>())
              .add(new Employee("Dick Newman", 35, 10000.00));
employeeDOJMap.forEach((year,empList)-> System.out.println(year+"-->"+empList));
 OUTPUT of the above code
Java 7 way of adding a new key(2017) in a multi-value map
2016–>[Employee Name: Ethan Hardy  Age: 65  Salary: 8000.0, Employee Name: Nancy Smith  Age: 22  Salary: 12000.0]
2017–>[Employee Name: Tom Newman  Age: 45  Salary: 12000.0]
2014–>[Employee Name: Deborah Sprightly  Age: 29  Salary: 9000.0]
2015–>[Employee Name: Tom Jones  Age: 45  Salary: 7000.0, Employee Name: Harry Major  Age:25  Salary: 10000.0]
Using Map.computeIfAbsent() to add a new key(2018) in a multi-value map
2016–>[Employee Name: Ethan Hardy  Age: 65  Salary: 8000.0, Employee Name: Nancy Smith  Age: 22  Salary: 12000.0]
2017–>[Employee Name: Tom Newman  Age: 45  Salary: 12000.0]
2018–>[Employee Name: Dick Newman  Age: 35  Salary: 10000.0]
2014–>[Employee Name: Deborah Sprightly  Age: 29  Salary: 9000.0]
2015–>[Employee Name: Tom Jones  Age: 45  Salary: 7000.0, Employee Name: Harry Major  Age:25  Salary: 10000.0]
Explanation of the code

  • The red code shows the Java 7 way of adding an employee named Tom Newman for the key 2017. It is a 5-6 line code.
  • The green code shows Java 8’s computeIfAbsent() method usage. A lambda expression implementing the BiFunction logic is passed to the method which instantiates and an ArrayList instance if it is empty(i.e. absent). To this ArrayList instance is added the new employee named Dick Newman. It is a one line code.
  • The output shows the employeeDOJMap printed using map.forEach() method after each of the two additions. This is shown to see that there is no difference in the way the new entries(key-value pairs) in the multi-value map are stored when using the Java 7 way and the Java 8 computeIfAbsent() method.

Java 8’s new Map.computeIfPresent() method
When working with multi-value maps, there are scenarios when while deleting a [key,value] pair, we might be removing the last/only value in the collection stored as the value for that key. In such cases, after removing the value from the collection, we would want to release the memory occupied by that the empty collection by removing that key from the multi-value map itself. This would require us to check after ‘every’ removal whether the value being removed is the last value for that key, and if so, remove the key from the multi-value map.

Map.computeIfPresent() takes away exactly this overhead of checking after every removal by reducing it to a simple one-line code. Map.computeIfPresent() method is defined as –

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

Where,
     – key is the first parameter which is the key of the multi-value map.
     – remappingFunction is an instance of a BiFunction. It computes and returns a value. In case of multi-value maps, the outcome of the key’s collection is decided based the value returned by this function. I.e. whether to keep the collection(if the collection is returned) or delete the collection(if a null value is returned).

To understand the usage of computeIfPresent() method better, let us have a look at the code sample below to understand the difference in code when using Java 7 versus Java 8’s computeIfPresent() method. As we did for the previous method, we will use color coding to differentiate between the Java 7 and Java 8 code. The red code is Java 7 way of checking and removing a key after every removal from the map. The green code is Java 8 way of doing the same thing using Map.computeIfPresent() method.

Java 8 code to show usage of Map.computeIfPresent() method
System.out.println("\nJava 7 way of removing a key(2017) in a multi-value map for which no entry exists\n");
List empListDel = employeeDOJMap.get(2017);
empListDel.removeIf(employee -> employee.getName().equals("Tom Newman"));
if (empListDel.size() == 0) {
  employeeDOJMap.remove(2017);
}
employeeDOJMap.forEach((year, empList)-> System.out.println(year+"-->"+empList));
System.out.println("\nUsing Map.computeIfPresent() to remove a key(2018) for which no entry exists\n");
employeeDOJMap.computeIfPresent(2018, (year, empList) -> empList.removeIf(employee -> employee.getName().equals("Dick Newman")) && empList.size() == 0 ? null : empList);
employeeDOJMap.forEach((year, empList)-> System.out.println(year+"-->"+empList));
 OUTPUT of the above code
Java 7 way of removing a key(2017) in a multi-value map for which no entry exists
2016–>[Employee Name: Ethan Hardy  Age: 65  Salary: 8000.0, Employee Name: Nancy Smith  Age: 22  Salary: 12000.0]
2018–>[Employee Name: Dick Newman  Age: 35  Salary: 10000.0]
2014–>[Employee Name: Deborah Sprightly  Age: 29  Salary: 9000.0]
2015–>[Employee Name: Tom Jones  Age: 45  Salary: 7000.0, Employee Name: Harry Major  Age:25  Salary: 10000.0]
Using Map.computeIfPresent() to remove a key(2018) for which no entry exists
2016–>[Employee Name: Ethan Hardy  Age: 65  Salary: 8000.0, Employee Name: Nancy Smith  Age: 22  Salary: 12000.0]
2014–>[Employee Name: Deborah Sprightly  Age: 29  Salary: 9000.0]
2015–>[Employee Name: Tom Jones  Age: 45  Salary: 7000.0, Employee Name: Harry Major  Age:25  Salary: 10000.0]
Explanation of the code

  • The red code shows the Java 7 way of removing the newly added employee for 2017 named Tim Newman. List.removeIf() method is used for finding out the employee and removing him. It is a 4-5 line code.
  • The green code shows Java 8 way of removing the only employee stored in the List for year 2018Dick Newman. It uses the computeIfPresent() method, to which a lambda expression equivalent to the BiFunction implementation is passed. This lambda checks if the list is empty after removing the aforesaid employee. If the list is empty the lambda returns a null else it returned the empList object itself. The computeIfPresent() method deletes the entry for the key 2018 if null is returned. This is a one line code, abeit a long line of code.
  • The output shows the employeeDOJMap printed using map.forEach() method after each of the two additions. This is shown to see that there is no difference in the way entries(key-value pairs) are removed from the multi-value map when using the Java 7 way as compared to the Java 8 computeIfAbsent() way.

Map.getOrDefault() method
Map.getOrDefault() method has been designed for scenarios where the value returned for a given key might be null i.e. the given key is not present in the map. In case of multi-value maps, it gives the programmer a utility to avoid NullPointerException at runtime by instantiating a new collection instance and returning it in case the key is not present and a null-value would otherwise have been returned.

Map.getOrDefault() is defined as follows –

default V getOrDefault(Object key, V defaultValue)

Where,
     – key is the first parameter which is the key of the multi-value map.
     – defaultValue is the value which will be used as default in case the key is not present and a null is returned.

To understand the working of the Map.getOrDefault() method better letter look at the code snippet showing its usage-

Java 8 code to show usage of Map.getOrDefault() method
System.out.println("\nAvoiding a null return when fetching a non-existent key's entry using Map.getOrDefault() method\n");
List<Employee> empList2019 = employeeDOJMap.getOrDefault(2019, new ArrayList<>());
System.out.println("Size of empList 2019 = " + empList2019.size());
 OUTPUT of the above code
Avoiding a null return when fetching a non-existent key’s entry using Map.getOrDefault() method
Size of empList 2019 = 0
Explanation of the code

  • getOrDefault() method is used to fetch the value for the key 2019. The default value is set as a new empty ArrayList().
  • Since the key 2019 does not exist, the empty ArrayList is returned instead. The size of this ArrayList is then printed. As expected the value of size is printed as 0.

Conclusion
In this tutorial we started by understanding the concept of multi-value maps. We then defined the multi-value map we used throughout the tutorial as we understood the definition and usage of the new methods added in Map interface in Java 8. The new Map methods we covered in detail are – forEach(), replaceAll(), computeIfAbsent(), computeIfPresent() and getOrDefault() method. With this we conclude the 4-part series covering the Java 8 Collection enhancements.

 

Digiprove sealCopyright © 2014-2017 JavaBrahman.com, all rights reserved.