Unravel › Java concepts › Collections and streams
Map idioms: merge, compute, entrySet
A handful of Map methods replace the clumsy "check, get, put" dance: merge for counting, computeIfAbsent for grouping, getOrDefault for lookups with a default, and entrySet for looping over keys and values together.
Learn these first
At a glance
A tally sheet with helper stamps: "add one to this name's row, creating the row first if there is none" is a single stamp instead of five steps.
- 1Counting: counts.merge(word, 1, Integer::sum)Grouping: groups.computeIfAbsent(key, k -> new ArrayList<>()).add(item).
- 2Default: map.getOrDefault(k, 0)Insert only if missing: putIfAbsent(k, v). Update only if present: computeIfPresent(k, (key, old) -> old + 1).
- 3Looping: for (Map.Entry<String, Integer> e : map.entrySet()) gives key and value in one pass; keySet() plus get(key) does a second lookup per keymap.forEach((k, v) -> ...) is the lambda form.
- 4Map.of(k1, v1, k2, v2) builds a small immutable map; Map.entry(k, v) is an immutable pair
- keySet(), values() and entrySet() are live views: removing from them removes from the map, and changing the map while iterating a view throws ConcurrentModificationException (unless you remove through the iterator)
- In computeIfAbsent the function must not modify the same map; HashMap can throw ConcurrentModificationException and ConcurrentHashMap can deadlock
- HashMap.merge and compute* find the bucket once and update in place, so they are also faster than separate get and put calls
import java.util.*;public class Words { static Map<String, Integer> count(List<String> words) { Map<String, Integer> counts = new TreeMap<>(); for (String w : words) counts.merge(w, 1, Integer::sum); return counts; } static Map<Integer, List<String>> byLength(List<String> words) { Map<Integer, List<String>> groups = new HashMap<>(); for (String w : words) groups.computeIfAbsent(w.length(), k -> new ArrayList<>()).add(w); for (Map.Entry<Integer, List<String>> e : groups.entrySet()) System.out.println(e.getKey() + " " + e.getValue()); return groups; }}- line 5 uses merge, the core of Map idioms: merge, compute, entrySet
- line 10 uses compute, the core of Map idioms: merge, compute, entrySet
- line 11 uses entrySet, the core of Map idioms: merge, compute, entrySet
Read the full explanation (about 1 min)
A handful of Map methods replace the clumsy "check, get, put" dance: merge for counting, computeIfAbsent for grouping, getOrDefault for lookups with a default, and entrySet for looping over keys and values together.
A mental model
A tally sheet with helper stamps: "add one to this name's row, creating the row first if there is none" is a single stamp instead of five steps.
How it works
Counting: counts.merge(word, 1, Integer::sum). Grouping: groups.computeIfAbsent(key, k -> new ArrayList<>()).add(item).
Default: map.getOrDefault(k, 0). Insert only if missing: putIfAbsent(k, v). Update only if present: computeIfPresent(k, (key, old) -> old + 1).
Looping: for (Map.Entry<String, Integer> e : map.entrySet()) gives key and value in one pass; keySet() plus get(key) does a second lookup per key. map.forEach((k, v) -> ...) is the lambda form.
Map.of(k1, v1, k2, v2) builds a small immutable map; Map.entry(k, v) is an immutable pair.
Going deeper
keySet(), values() and entrySet() are live views: removing from them removes from the map, and changing the map while iterating a view throws ConcurrentModificationException (unless you remove through the iterator).
In computeIfAbsent the function must not modify the same map; HashMap can throw ConcurrentModificationException and ConcurrentHashMap can deadlock.
merge removes the entry when the function returns null, which is useful but also a surprise.
Under the hood
HashMap.merge and compute* find the bucket once and update in place, so they are also faster than separate get and put calls. ConcurrentHashMap makes the same methods atomic.
Common mistakes
if (!map.containsKey(k)) map.put(k, ...)(two lookups, and not atomic).- Modifying the map inside the loop over its entries.
- Looking up
get(k)inside akeySetloop.
How Unravel recognises it
When you paste code, Unravel looks for these signs of map idioms: merge, compute, entryset. Spotting them yourself is a good habit when you read unfamiliar code:
- Method calls:
entrySet()keySet()putIfAbsent()merge()computeIfPresent()compute()replaceAll()Map.entry()Map.ofEntries()getKey()getValue()values()
Part of a learning path
- Collections in depth · previous: How HashMap works · next: Sets: HashSet, LinkedHashSet, EnumSet
Related concepts
Frequently asked questions
What is Map idioms: merge, compute, entrySet?
A handful of Map methods replace the clumsy "check, get, put" dance: merge for counting, computeIfAbsent for grouping, getOrDefault for lookups with a default, and entrySet for looping over keys and values together.
What are the most common mistakes with Map idioms: merge, compute, entrySet?
if (!map.containsKey(k)) map.put(k, ...) (two lookups, and not atomic). Modifying the map inside the loop over its entries. Looking up get(k) inside a keySet loop.
How can I see Map idioms: merge, compute, entrySet in real code?
Paste your own Java into Unravel and it will point out where map idioms: merge, compute, entryset appears, explain how it works at the detail level you choose, and draw the flow of the code around it.