Map.computeIfAbsent(key, function) looks up a key and, when it has no non-null associated value, invokes the function and associates its non-null result. It replaces an existing null mapping when the function yields a value; when the function returns null, it leaves an existing null mapping in place rather than removing it. This differs from compute(), whose null result removes a mapping. section 8.4
A common use is lazily creating a collection for a multimap. It replaces separate get() and put() calls with one operation, avoiding a second map search; optimized implementations can use the lookup position already found. HashMap has provided this optimization since Java 8, and TreeMap since Java 15. section 8.12
static void addToMultiMap(Map<String, List<String>> map,
String key,
String value) {
map.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
}
Use computeIfAbsent to create a list only for a missing key, then add the value to the returned list. section 8.12
Do not structurally modify the same map from the mapping function. The function runs after the map finds an insertion location but before insertion; another modification can invalidate that location. In Java 8, this could corrupt a HashMap. Since Java 9, HashMap can detect the modification and throw ConcurrentModificationException. section 8.12
Recursive cache dependency
Caching transitive expression references with computeIfAbsent can recurse into the cache while calculating a missing entry. A request for one variable may calculate referenced variables and insert their results before the outer call completes. The failure can depend on evaluation order, so tests that warm the cache bottom-up may pass while top-down evaluation exposes the modification. section 8.12
final Map<String, Set<String>> refCache = new HashMap<>();
Set<String> transitiveRefs(String variable) {
return refCache.computeIfAbsent(variable, this::calc);
}
private Set<String> calc(String variable) {
Set<String> result = new HashSet<>();
String expression = map.getOrDefault(variable, "");
for (String ref : immediateRefs(expression)) {
result.add(ref);
result.addAll(transitiveRefs(ref));
}
return result;
}
This cache calls transitiveRefs from inside the computeIfAbsent mapping function, causing nested modification of refCache. section 8.12
Use computeIfAbsent safely by following these rules:
- Keep the mapping function free of modifications to the map being computed; calculate nested cache entries outside that operation when recursion can occur. section 8.12
- Avoid null values in maps where possible. If null must represent a meaningful state, use a wrapper such as Optional and account for computeIfAbsent invoking its function for an existing null mapping. section 8.4
- For concurrent maps, prefer one Map operation over a get-and-put sequence when the task needs an atomic update; choose merge for incrementing counts. section 9.12
The method is also relevant to concurrent code, but its safety is not a license to compose arbitrary operations. Separate get() and put() calls on a ConcurrentMap are non-atomic; Map update methods can express many such updates atomically when the concurrent-map implementation supports them. Choose the operation that matches the update: merge is the direct fit for accumulating counts. section 9.12
