Java 8 Map merge, putIfAbsent and forEach with Examples

Introduction

This tutorial covers the Java 8 additions to the java.util.Map interface that are not part of the computeIfAbsent, computeIfPresent and getOrDefault tutorialClick to read: computeIfAbsent, computeIfPresent and getOrDefault tutorial: merge(), putIfAbsent(), forEach(), replaceAll(), and the conditional remove(key, value) and replace(key, oldValue, newValue). We will start with how merge() decides what to store, since it is the most useful and least understood of them. We will then go through each method's definition, run one example that uses them all, and finish with a table that helps choose between merge(), putIfAbsent() and the compute methods.

Why Java 8 added these methods

Before Java 8, updating a map entry needed several calls and an if: check whether the key is present, read the old value, compute the new one, and put it back. Code like that is long, easy to get wrong, and - on a shared ConcurrentHashMap - not atomic, because another thread can change the entry between the check and the put. The new methods are all default methods on Map, so every map implementation got them, and concurrent maps implement them atomically. Each takes a lambda expressionClick to read: lambda expression for the "what should the new value be" part.

How merge() works

merge() is the method for "combine a new value with whatever is already there": counting, summing, concatenating. If the key has no value, the given value is stored. If it has one, a function combines the old and new values - and if that function returns null, the entry is removed.
How Java 8 Map.merge works - puts the value for a new key, combines old and new values with a function, removes the entry when the function returns null

The methods and their definitions

  • V merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction): as in the diagram. Returns the new value, or null if the entry was removed. The value argument must not be null.
  • V putIfAbsent(K key, V value): stores the value only if the key has no value (or is mapped to null). Returns the existing value, or null if it stored the new one.
  • void forEach(BiConsumer<? super K,? super V> action): calls the action with each key and value - shorter than looping over entrySet() and calling getKey() and getValue().
  • void replaceAll(BiFunction<? super K,? super V,? extends V> function): replaces every value with the function's result for that key and value.
  • boolean remove(Object key, Object value): removes the entry only if the key is currently mapped to that value.
  • boolean replace(K key, V oldValue, V newValue): replaces the value only if it is currently oldValue. A two-argument replace(key, value) also exists, which replaces only if the key is present.
BiFunction and BiConsumer are functional interfaces with two arguments; see the BiFunction, BiConsumer and BiPredicate tutorialClick to read tutorial: Java 8 BiFunction, BiConsumer and BiPredicate Tutorial.

Java 8 code example of the Map methods

The example uses TreeMaps so that the printed maps are sorted by key and easy to read.
Java 8 code showing Map merge, putIfAbsent, forEach, replaceAll, remove and replace
//MapMethods.java
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.BiFunction;

public class MapMethods {

  public static void main(String[] args) {
    // 1. merge(): count how often each skill appears
    List<String> skills = Arrays.asList("Java", "SQL", "Java", "Spring", "SQL", "Java");
    Map<String, Integer> skillCount = new TreeMap<>();
    for (String skill : skills) {
      skillCount.merge(skill, 1, Integer::sum);
    }
    System.out.println("1. merge counts: " + skillCount);

    // 2. merge() returning null removes the entry
    Map<String, Integer> leaveBalance = new TreeMap<>();
    leaveBalance.put("Tom Jones", 3);
    leaveBalance.put("Harry Major", 10);
    BiFunction<Integer, Integer, Integer> deduct =
        (left, taken) -> left - taken == 0 ? null : left - taken;
    leaveBalance.merge("Tom Jones", 3, deduct);
    leaveBalance.merge("Harry Major", 3, deduct);
    System.out.println("2. after taking 3 days each: " + leaveBalance);

    // 3. putIfAbsent(): set a default only when there is no value yet
    Map<String, String> managerOf = new TreeMap<>();
    managerOf.put("Tom Jones", "Deborah Sprightly");
    String previous1 = managerOf.putIfAbsent("Tom Jones", "Ethan Hardy");
    String previous2 = managerOf.putIfAbsent("Nancy Smith", "Ethan Hardy");
    System.out.println("3. putIfAbsent returned " + previous1 + " and " + previous2
        + " -> " + managerOf);

    // 4. forEach((key, value) -> ...) instead of looping over entrySet()
    Map<String, Double> salaries = new TreeMap<>();
    salaries.put("Tom Jones", 5200.0);
    salaries.put("Harry Major", 4100.0);
    salaries.put("Nancy Smith", 5800.0);
    System.out.print("4. forEach:");
    salaries.forEach((name, salary) -> System.out.print(" " + name + "=" + salary));
    System.out.println();

    // 5. replaceAll((key, value) -> newValue): a 5% raise for everyone
    salaries.replaceAll((name, salary) -> salary * 1.05);
    System.out.println("5. after replaceAll: " + salaries);

    // 6. Conditional remove(key, value) and replace(key, oldValue, newValue)
    Map<String, String> status = new HashMap<>();
    status.put("E-1001", "ACTIVE");
    boolean replaced = status.replace("E-1001", "ON_LEAVE", "ACTIVE");
    boolean removed = status.remove("E-1001", "ACTIVE");
    System.out.println("6. replace(ON_LEAVE -> ACTIVE): " + replaced
        + ", remove(ACTIVE): " + removed + ", map now " + status);
  }
}
 OUTPUT of the above code
1. merge counts: {Java=3, SQL=2, Spring=1}
2. after taking 3 days each: {Harry Major=7}
3. putIfAbsent returned Deborah Sprightly and null -> {Nancy Smith=Ethan Hardy, Tom Jones=Deborah Sprightly}
4. forEach: Harry Major=4100.0 Nancy Smith=5800.0 Tom Jones=5200.0
5. after replaceAll: {Harry Major=4305.0, Nancy Smith=6090.0, Tom Jones=5460.0}
6. replace(ON_LEAVE -> ACTIVE): false, remove(ACTIVE): true, map now {}

Explanation of the code

  • Part 1: the first time a skill is seen, merge() stores 1; after that, Integer::sum adds 1 to the stored count. This is the standard one-line way to count occurrences with a map.
  • Part 2: the remapping function subtracts the days taken and returns null when nothing is left. Tom had exactly 3 days, so his entry was removed; Harry's balance went from 10 to 7.
  • Part 3: Tom already has a manager, so putIfAbsent() left it unchanged and returned it. Nancy had none, so the default was stored and null was returned. The return value tells you which of the two happened.
  • Part 4: forEach() hands the lambda each key and value directly. With a TreeMap the entries come in key order; with a HashMap the order is not defined.
  • Part 5: replaceAll() gives everyone a 5% raise in place, without creating a new map.
  • Part 6: the conditional methods check the current value first. replace() did nothing because the status was not ON_LEAVE; remove() succeeded because it was ACTIVE. On a ConcurrentHashMap these checks and changes happen as one atomic step, which makes them useful for simple lock-free updates.

Choosing the right method

  • Add to or combine with an existing value (counts, sums, lists of names): merge().
  • Store a default only if nothing is there, and the default is cheap to create: putIfAbsent().
  • Store a value that is costly to create, only when it is missing - for example a new ArrayList to add to: computeIfAbsent(), which calls its function only when needed. putIfAbsent(key, new ArrayList<>()) creates the list every time, even when it is thrown away.
  • Change a value only if the key is present: computeIfPresent().
  • Calculate a new value from the key and the old value, whether present or not: compute().
  • Read with a fallback, without changing the map: getOrDefault().
  • Transform every value: replaceAll(); to just read every entry: forEach().

Summary

In this tutorial we first saw why Java 8 added update methods to Map: shorter code, fewer mistakes, and atomic updates on concurrent maps. Next we saw how merge() stores, combines or removes a value, and the definitions of merge(), putIfAbsent(), forEach(), replaceAll() and the conditional remove() and replace(). We then ran an example that counted skills, removed a used-up leave balance, set default managers, printed and raised salaries, and changed a status conditionally. Lastly, we listed which method to use for which job, including the compute methods.