Java 8 Map merge, putIfAbsent and forEach with Examples
Introduction
This tutorial covers the Java 8 additions to thejava.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 anif: 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.
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, ornullif the entry was removed. Thevalueargument must not benull.V putIfAbsent(K key, V value): stores the value only if the key has no value (or is mapped tonull). Returns the existing value, ornullif it stored the new one.void forEach(BiConsumer<? super K,? super V> action): calls the action with each key and value - shorter than looping overentrySet()and callinggetKey()andgetValue().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 currentlyoldValue. A two-argumentreplace(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 usesTreeMaps 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);
}
}
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::sumadds 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
nullwhen 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 andnullwas returned. The return value tells you which of the two happened. - Part 4:
forEach()hands the lambda each key and value directly. With aTreeMapthe entries come in key order; with aHashMapthe 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 notON_LEAVE;remove()succeeded because it wasACTIVE. On aConcurrentHashMapthese 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
ArrayListto 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 toMap: 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.Java 8 Tutorials
Java 8 Custom Collector with Collector.of() TutorialClick to read tutorial: Java 8 Custom Collector with Collector.of() Tutorial Java 8 - How to Sort a Map by Key or ValueClick to read tutorial: Java 8 - How to Sort a Map by Key or Value Java 8 CompletableFuture Tutorial with ExamplesClick to read tutorial: Java 8 CompletableFuture Tutorial with Examples Java 8 CompletableFuture: Combining and Error HandlingClick to read tutorial: Java 8 CompletableFuture: Combining and Error Handling Java 8 Parallel Streams Tutorial with ExamplesClick to read tutorial: Java 8 Parallel Streams Tutorial with Examples Java 8 Collectors.toMap Tutorial with ExamplesClick to read tutorial: Java 8 Collectors.toMap Tutorial with Examples Java 8 BiFunction, BiConsumer and BiPredicate TutorialClick to read tutorial: Java 8 BiFunction, BiConsumer and BiPredicate Tutorial
Java 8 Custom Collector with Collector.of() TutorialClick to read tutorial: Java 8 Custom Collector with Collector.of() Tutorial Java 8 - How to Sort a Map by Key or ValueClick to read tutorial: Java 8 - How to Sort a Map by Key or Value Java 8 CompletableFuture Tutorial with ExamplesClick to read tutorial: Java 8 CompletableFuture Tutorial with Examples Java 8 CompletableFuture: Combining and Error HandlingClick to read tutorial: Java 8 CompletableFuture: Combining and Error Handling Java 8 Parallel Streams Tutorial with ExamplesClick to read tutorial: Java 8 Parallel Streams Tutorial with Examples Java 8 Collectors.toMap Tutorial with ExamplesClick to read tutorial: Java 8 Collectors.toMap Tutorial with Examples Java 8 BiFunction, BiConsumer and BiPredicate TutorialClick to read tutorial: Java 8 BiFunction, BiConsumer and BiPredicate Tutorial