liveBook · Manning

Map.computeIfAbsent

As explained in 100 Java Mistakes and How to Avoid Them

Map.computeIfAbsent is a Java Map operation that computes and associates a value for a key when no non-null value is already associated with that key.

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

How 100 Java Mistakes and How to Avoid Them covers it

How deep it goes
Explains lookup optimization, null behavior, and reentrant modification consequences through multimap and recursive-cache implementations.
Examples
Uses multimap insertion and a recursive expression-reference cache evaluated in bottom-up and top-down orders.
What you can do after
Apply it via the Map API to populate missing mappings while avoiding null and reentrant-modification traps.
How it connects
Connects Map, HashMap, TreeMap, null values, ConcurrentMap, compute, and merge.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Java Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.