Java HashMap compute: In-Place Value Updates
java hashmap compute: Learn how HashMap.compute updates map entries atomically with a remapping function, when to use it, and how it compares to merge and computeIfAbs...
java hashmap compute requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
The compute method on java.util.HashMap lets you update a value in place using a remapping function. When you call map.compute(key, remappingFunction), the function receives the current key and the current value (or null if the key is absent), and returns a new value. If the returned value is null, the entry is removed. This makes compute a compact way to implement read-modify-write logic that would otherwise require several lines of get, put, and null checks.
The compute Method Signature and Behavior
The signature is:
V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
The remappingFunction is applied to the key and the current value. If the key is not present, the current value is null. The function's return value becomes the new mapping. If the function returns null, the entry is removed (or not added if the key was absent). This behavior is consistent across HashMap and ConcurrentHashMap, though the concurrency guarantees differ.
Basic Usage Example
Suppose you are counting word frequencies. With compute, you can increment the count in one line:
Map<String, Integer> counts = new HashMap<>(); String word = "apple"; counts.compute(word, (key, count) -> count == null ? 1 : count + 1);
The lambda receives the existing count or null if the word has not been seen yet. This avoids the verbose pattern of checking containsKey and then calling put. It also avoids a separate get call that could race with other mutations in a concurrent context.
When compute Is Better Than get + put
The manual approach looks like this:
Integer current = map.get(key); int newValue = (current == null) ? 1 : current + 1; map.put(key, newValue);
This works in a single-threaded environment, but it is not atomic. Between the get and the put, another thread could modify the same key, causing a lost update. compute performs the read and write as a single atomic operation on ConcurrentHashMap. Even on a plain HashMap, it reduces the chance of logic errors by keeping the update logic in one place.
compute vs merge vs computeIfAbsent
These three methods are often confused because they all take a remapping function. The difference lies in when the function is called and what it receives.
| Method | Function receives | Function called when key absent | Removes entry if function returns null |
|---|---|---|---|
compute | key and current value (or null) | Yes | Yes |
computeIfAbsent | key only | Yes | No (function result is never null for absent key) |
merge | current value (or null) and given value | Yes (with given value) | Yes |
computeIfAbsent is useful when you only want to initialize a value if the key is missing. merge is useful when you have an incoming value and want to combine it with the existing one, such as adding to a sum. compute gives you full control over the entire mapping, including the ability to remove the entry.
Edge Cases and Common Pitfalls
One common pitfall is assuming that compute will never remove an entry. If your remapping function returns null, the entry is removed. This can surprise developers who use compute for incrementing and accidentally return null for a zero value. For example:
map.compute(key, (k, v) -> (v == null || v == 0) ? null : v - 1);
This decrements the value but removes the key when it reaches zero. If that is not intended, you must return a non-null value, such as 0.
Another edge case is when the key exists but the value is null. HashMap allows null values. In that case, the remapping function receives null as the current value, which is indistinguishable from an absent key. Your function must handle both cases consistently.
Concurrency Considerations
HashMap is not thread-safe. Using compute on a shared HashMap from multiple threads can still corrupt the map or cause infinite loops during resizing. If you need atomic updates in a concurrent environment, use ConcurrentHashMap. Its compute method is atomic and guarantees that the remapping function is applied exactly once per key. This is particularly valuable for caches or counters that are updated from multiple threads.
However, the remapping function itself must be fast and must not modify the same map directly. ConcurrentHashMap locks the bin containing the key, so a slow function will block other updates to that bin. Also, the function should not call other methods on the same map, as this can cause a IllegalStateException or deadlock in some implementations.
Performance and Maintainability
From a performance perspective, compute is not a magic bullet. It still requires hashing the key and potentially resizing the map. The main benefit is code clarity and atomicity in concurrent cases. Overusing compute for simple operations that are better expressed with putIfAbsent or merge can make the code harder to read. Use compute when you need to inspect or modify the current value based on its existing state, and use merge when you have a separate value to combine.
Maintainability improves because the update logic is contained in one lambda, but be careful not to make the lambda too complex. If the remapping function grows beyond a few lines, extract it into a named method for readability.
A Practical Example: Building a Cache with Expiry
Consider a simple cache that stores a timestamp and a value. You want to update the value only if the new timestamp is newer than the stored one. compute handles this cleanly:
Map<String, CacheEntry> cache = new ConcurrentHashMap<>(); cache.compute(key, (k, entry) -> { if (entry == null || newEntry.timestamp() > entry.timestamp()) { return newEntry; } return entry; });
If the key is absent, entry is null and the new entry is stored. If the existing entry has an older timestamp, it is replaced. Otherwise, the old entry is kept. The entire check-and-set operation is atomic on a ConcurrentHashMap, preventing lost updates when multiple threads write the same key simultaneously.
This pattern is common in distributed caches and session stores where you need to avoid overwriting newer data with stale data. Without compute, you would need to synchronize externally or risk race conditions.