Friday, January 27, 2023

How to use Map.compute(), computeIfPresent() and ComputeIfAbsent in Java? HashMap and ConcurrentHashMap Example

The JDK 8 has added several useful methods in existing interfaces e.g. java.util.Map, java.util.Collection, and java.util.concurrent.ConcurrentMap. Thanks to default methods, the much-needed evolution of existing interfaces becomes possible. Out of many useful methods, one method which stands out to me is the compute() method, which allows you to update a value in ConcurrentHashMap atomically. As per Java documentation, The compute() function tries to compute a mapping for the specified key and its current mapped value (or null if there is no current mapping). The entire function is performed atomically.

Some attempted update operations on this map by other threads may be blocked while the computation is in progress, so the computation should be short and simple, and must not attempt to update any other mappings of this Map. In simple words, you can use this method to atomically update a value for a given key in the ConcurrentHashMap.

You can use the compute() method to update an existing value inside ConcurrenHashMap. For example, to either create or append a String msg to a value mapping:

map.compute(key, (k, v) -> (v == null) ? msg : v.concat(msg))
If the function returns null, the mapping is removed (or remains absent if initially absent).

If the function itself throws an (unchecked) exception, the exception is rethrown, and the current mapping is left unchanged

There are also variants of compute() method like computeIfPresent() and computeIfAbsent(), which computes the new value only if an existing value is present or absent.

For example, you can update a map of LongAdder using computeIfAbsent as shown below:

map.computeIfAbsent(key, k -> new LongAdder()).increment();

Here the constructor of LongAdder class will only be called when a new counter is actually needed. If a value exists then it is returned from ConcurrentHashMap, similar to the putIfAbsent() method.  You can further see The Complete Java MasterClass course on Udemy by Tim Buchalaka and his team to learn more about new methods added to the existing API in Java 8.




How to use compute() method in Java 8? Example

Here is a Java program to demonstrate how to use the compute() method to atomically update a value for a given key in a ConcurrentHashMap.

How to use Map.compute(), computeIfPresent() and ComputerIfAbsent in Java? ConcurrentHashMap Example


Now, let's see the code in action:

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.LongAdder;

/*
 * Java Program to use compute() method of Java 8
 * to atomically update values in ConcurrentHashMap
 */
public class Hello {

  public static void main(String[] args) throws Exception {

    // a ConcurrentHashMAp of string keys and Long values
    ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();
    map.put("apple", 3);
    map.put("mango", 4);
    
    System.out.println("map before calling compute: " + map);

    // in JDK 8 - you can also use compute() and lambda expression to
    // atomically update a value or mapping in ConcurrentHashMap
    map.compute("apple", (key, value) -> value == null ? 1 : value + 1);
    
    System.out.println("map after calling compute on apple: " + map);

    // you can also use computeIfAbsent() or computeIfPresent() method
    // Constructor of LongAdder will be only called when a value for
    // given key is not exists
    ConcurrentMap<String, LongAdder> map2 = new ConcurrentHashMap<>();
       
    System.out.println("map with LongAdder before calling compute: " + map2);
    
    map2.computeIfAbsent("apple", key -> new LongAdder()).increment();
    map2.computeIfAbsent("mango", key -> new LongAdder()).increment();
    map2.computeIfAbsent("apple", key -> new LongAdder()).increment();
    
    System.out.println("map with LongAdder after calling compute on 
                            apple, mango, apple: " + map2);

  }
}

Output:
map before calling compute: {apple=3, mango=4}
map after calling compute on apple: {apple=4, mango=4}
map with LongAdder before calling compute: {}
map with LongAdder after calling compute on apple, mango, apple: {apple=2, mango=1}

How does compute() and computeIfPresent works in Java


Now, let's try to understand what's happening here.

In the first line, we have simply created a Concurrent HashMap with two keys apple and mango, and their values are their count like how many apples we have and how many mangoes we have.

Now, we got one more apple and we need to update the count on our bucket, for that, we are calling the compute() method as shown below:

map.compute("apple", (key, value) -> value == null ? 1 : value + 1);

This is just incrementing the value for this key, hence the count of "apple" moved from 3 to 4, which is shown in our second print statement.

In the second example, we have an empty ConcurrentHashMap and our job is to add keys and their counts in real-time. This is suited for scenarios like you are doing a sale and you need to keep track of how many copies of a particular book or course are sold.

Another useful scenario is reading through a text file and printing the count of each word that appears in the file.

Anyway, the key point here is that the map is initially empty which is shown in the third print statement. After that, we have added 2 apples and 1 mango to this map using computeIfAbsent() method and it has created a LongAdder object for each key and stored that as a value.

The good thing about this method is that the LongAdder object is only created when a key is first time added i.e. it was absent initially, after that, the count is incremented in the LongAdder object.

This is a much better way to increase a counter in a concurrent Java application, but LongAdder is only available from Java 8.

That's why when we printed map2, we see that there are two apples and 1 mango is present in our concurrent map. You can further see The Ultimate Java 8 Tutorial - From beginner to professional to learn more about LongAdder and several other useful new classes added in JDK 8 API.

Here is the screenshot of this program with output:

Java 8 compute() and computeIfPresent() Example


Important points about compute() method of Java 8

Now that you know what is compute() method and what does it do and how to use it in a Java program, it's time to revise some of the important things about this method.

Here are a couple of points which is worth remembering:

1) It allows you to atomically update a value in ConcurrentHashMap.

2) It has variants like computeIfAbsent() and computeIfPresent() which computes values accordingly.

3) You can also use merge() in place of the compute() for updating an existing value.


That's all about how to use the compute() method in Java 8. You can use this method to update the values of a particular key in ConcurrentHashMap in Java atomically. That's the main thing, it allows atomic insert and update without any explicit synchronization. 

You can also use it to insert count for new keys. It's particularly useful for populating a concurrent hash map with keys and their counts e.g. reading a text file and print counts of all words. 

You can also use this to solve many coding problems like finding duplicates in String or finding duplicates in array where duplicate can be identified when count is more than 1.  Both computeIfPresent() and computeIfAbsent() complements the compute() method and its important for Java developers to learn them. 


Other Java 8 tutorials you may like:

Thanks for reading this article so far. If you like this Java 8 Computer and ComputeIfAbsetnt of HashMap tutorial then please share with your friends and colleagues, if you have any questions or doubt then please drop a comment.

P. S. - If you are looking for some free courses to learn recent changes on Java 8 and Java 9 then you can also see this list of Free Java 8 and 9 Courses for Programmers.

No comments:

Post a Comment

Feel free to comment, ask questions if you have any doubt.