/ API

Map merge and compute, hidden API diamonds

If you’ve been working in Java since "quite some time", you probably are experienced in using Map objects. One very frequent use-case is to check if a Map contains a value:

  1. Get the value
    • If the value is null, put an initial value.
    • If isn’t, transform the get value and put the new value into the map under the same key
Map<String, Integer> map = new HashMap<>();

// ...

Integer value = map.get(key);
if (value == null) {
    map.put(key, 1);
} else {
    map.put(key, ++value);
}

Java provides two abstractions for this, with slightly different semantics.

Merge

Since Java 1.8, Map offers a merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) method that accepts the following arguments:

  1. A key
  2. A default value. If there’s no value corresponding to the key or if the value is null, the method will associate this default value with the key
  3. A remapping BiFunction:
    1. The first argument is the current value mapped by the key
    2. The second argument is the default value
    3. The return value is the new computed value

The above code can be rewritten with merge() like the following:

map.merge(key, 1, (oldValue, defaultValue) -> oldValue + 1);

Additionally, if the value returned by the BiFunction is null, the method will remove the mapping.

Compute

compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) is another alternative available since Java 1.8. It accepts the following arguments:

  1. A key
  2. A remapping BiFunction:
    1. The first argument is the key
    2. The second argument is the current value
    3. The return value is the new computed value

Like merge(), if the value returned by the BiFunction is null, the mapping will be removed.

The above code can be rewritten with compute() like the following:

map.compute(key, (aKey, value) -> {
    if (value == null) {
        return 1;
    } else {
        return ++value;
    }
});

Differences between compute() and merge() are subtle but important. With the former, the BiFunction will be called in any case, while with the latter, it will only if the value is not null. Also, note that the BiFunction arguments refers to different arguments depending on the method.

If and only if the value is guaranteed to not be null, the above code can be rewritten as:

map.compute(key, (aKey, value) -> ++value);

To make sure this assumption holds true, a dedicated method is also available, computeIfPresent() wit the same arguments:

map.computeIfPresent(key, (aKey, value) -> ++value);

A counterpart is available, computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction). Note that the BiFunction is replaced by a simple Function, since there’s no need to pass the value as it is guaranteed to be null.

Conclusion

Every Java version adds new abstractions that allow to improve the way the code is written. A lot of methods were introduced on existing classes in Java 8 that leverage the new functional interfaces. Be sure to read about them, and see how they can make your code more readable: more about what, and less about how.

Nicolas Fränkel

Nicolas Fränkel

Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Currently working for Hazelcast. Also double as a trainer and triples as a book author.

Read More
Map merge and compute, hidden API diamonds
Share this