Java Map Compute and Merge Operations
Java `Map` methods such as `computeIfAbsent`, `computeIfPresent`, and `merge` express per-key updates without a separate lookup followed by mutation. With concurrent map implementations such as `ConcurrentHashMap`, these operations provide atomic per-key update behavior that can prevent common check-then-act races.
`computeIfAbsent` calculates and installs a value only when a key currently has no associated value. `computeIfPresent` recalculates a value only when the key is associated with a non-null value, while `compute` can recalculate the value regardless of whether the key was previously present. A remapping function returning `null` removes the mapping for the relevant compute operations.
`merge` is useful when an existing value should be combined with a new value. If the key is absent, the supplied value is installed directly; if it is present, the remapping function receives the old and new values and produces the replacement. Returning `null` from the remapping function removes the mapping.
These methods are especially important with `ConcurrentHashMap`: a sequence such as `if (!map.containsKey(k)) map.put(k, v)` is not a single atomic operation. A concurrent map's compute/merge methods encapsulate the read-modify-write action for the key, so competing threads do not need to implement that coordination themselves.
The mapping function should be short and side-effect-conscious. For `ConcurrentHashMap`, remapping functions may execute while other operations are occurring, and a function should not depend on recursively updating the same map in a way that can cause an illegal recursive update or unnecessary contention.
Choose the method based on the update rule: use `computeIfAbsent` for lazy initialization, `computeIfPresent` for conditional replacement, `compute` for a general key-based recalculation, and `merge` when combining an existing value with an incoming value.
Key points
- `computeIfAbsent` lazily creates a value for an absent key.
- `computeIfPresent` updates only an existing non-null mapping.
- `compute` gives the remapping function the key and current value, including `null` for an absent mapping.
- `merge` combines an existing value with a supplied value and removes the mapping if the remapping function returns `null`.
- With `ConcurrentHashMap`, these operations provide atomic per-key update semantics that avoid a separate check-then-act sequence.
Interview questions
When would you use `computeIfAbsent` instead of `putIfAbsent`?
Use `computeIfAbsent` when constructing the value requires a function of the key or should happen lazily. `putIfAbsent` takes an already-created value, so its construction can occur even when another value is already present. With `ConcurrentHashMap`, `computeIfAbsent` also expresses the per-key initialization as one concurrent map operation. The mapping function should remain small and avoid problematic side effects.
How does `merge` behave when a key is absent versus present?
If the key has no mapping, `merge` associates it with the supplied value without invoking the remapping function. If the key already has a non-null value, the remapping function combines the existing value and supplied value. The returned result becomes the new mapping. If that function returns `null`, the mapping is removed.
Why can `containsKey` followed by `put` be unsafe with `ConcurrentHashMap`?
Each individual operation can be thread-safe while the sequence as a whole is not atomic. Another thread can change the mapping between the check and the subsequent `put`, producing behavior that does not match the intended conditional update. A compute-style method expresses the condition and update as one map-level operation for the key. This is a classic check-then-act race.
What is the difference between `compute`, `computeIfPresent`, and `computeIfAbsent`?
`compute` invokes its remapping function based on the key's current mapping and can establish, replace, or remove a mapping. `computeIfPresent` limits the operation to a key that currently has a non-null mapping. `computeIfAbsent` invokes its function only when the key has no current mapping and can install the resulting value. All three can remove a mapping when their applicable remapping function returns `null`.
What concurrency concerns should you consider inside a `ConcurrentHashMap` remapping function?
The function participates in a concurrent map update and should therefore be short and avoid blocking work when possible. It should not assume that unrelated operations on external state become atomic merely because the map operation is coordinated. Recursive attempts to structurally update the same map can lead to illegal recursive updates or other problematic behavior. In production code, keep the remapping logic deterministic and narrowly focused on the value transition.