Java 8 brought functional programming to the language, using lambda expressions and stream processing to make coding operations elegant and simple.
The Map interface is a commonly used interface for hash tables and an important component of the collection framework. When adding, deleting, modifying, and querying keys and values, we often need to use nested if-else logic and built-in functions like contains(), put(), putIfAbsent(), putIfPresent(), remove(), making code redundant and complex.
Ugly Addition and Deletion#
For example, here’s some ugly and bloated code:
if (!map.containsKey(key)) { map.put(key, 1);} else { map.put(key, map.get(key) + 1);}This operation often appears when adding a new key. If the key doesn’t exist, we need to call put; if it exists, we need to get the value first, then put again.
Another example:
if (map.containsKey(key)) { map.put(key, map.get(key) - 1); if (map.get(key) == 0) { map.remove(key); }}This operation is also common when decrementing a value by 1 and removing the key if the value reaches 0. It frequently calls functions like containsKey(), put(), get(), making the code exceptionally bloated.
Simplified Operations#
The introduction of merge and compute in Java 8 directly eliminates this repetitive template code.
merge() - Replacing Old Values with New Values#
V merge(K key, V value, BiFunction<V, V, V> remappingFunction)The merge() method accepts a key, a default value, and a remappingFunction for when the value exists. It’s a BiFunction type interface that accepts three generic parameters T, U, R, representing two input types and one output type.
merge() operation:
- Key doesn’t exist → Insert value (newValue)
- Key exists → Merge (oldValue, newValue) into new value
- If remappingFunction returns null → Delete key
Therefore, the above addition operation can be simplified to:
map.merge(c, 1, Integer::sum);This means if the key corresponding to c doesn’t exist, insert (key, 1); otherwise insert (key, value+1), achieving an improvement over the ugly code.
compute() - Customizable Unified Update Entry#
compute() is generally used for complex logic processing, such as deleting keys or updating specific values based on certain conditions.
V compute(K key, BiFunction<K, V, V> remappingFunction)You can see that compute() has one less parameter than merge() - no default value. This doesn’t mean there’s no default value, but rather that the default value is directly replaced with the return value of the remappingFunction.
For example, if the remappingFunction returns 1, it sets the value to 1 and puts it; if it returns null, it deletes the entry.
So, the previous deletion code can be simplified to:
map.compute(c, (k, v) -> (v == null || v == 1) ? null : v - 1);Note the null-checking logic here. You need to specifically check if v == null (meaning the entry doesn’t exist), otherwise it would execute v-1 and cause an NPE. The code semantics are: if the value exists, subtract 1, and delete when it reaches 0. Therefore, you need to be more careful when using compute().
Summary#
If you can master these two functional programming methods, you’ll be able to handle hash table operations with ease.