Tuesday, September 26, 2023

Java 8 Compute(), ComputeIfAbsent() and ComputeIfPresent() Example Tutorial

Hello guys, if you have been doing Java development then you know that Java 8 brings a lot of changes not just on programming language part by introducing Lambda expression, default methods on interface, static method on interface but also on API  and SDK part like Stream API and new Date and Time API. But while those bigger changes get lot of coverage many small changes but very useful in day to day programming task doesn't get enough mention and many Java programmers are unaware of those. One of such enhancement was addition of compute(), computeIfAbsent(), and computeIfPresent() method in java.util.Map interface. Adding  a new method on an existing interface was not possible before Java 8 but new feature like default and static method on interface made it possible and Java designers take full advantage of these two feature to enhance and improve existing interface like Collection and Map. 

As part of that enhancement, computeIfAbsent, compute and computeIfPresent methods were added in Java 8 to improve the functionality and usability of the java.util.Map interface. 

These three methods now provides a convenient and efficient way to update values in a map based on some computation, and to make it easier to implement various functional programming patterns. 

The best thing about this method is that they update value atomically. 

Before Java 8 and the introduction of these methods, updating values in a map typically required using get to retrieve the current value, performing some computation on the value, and then using put to store the result back in the ma as shown below:

Integer  count = studentVsBookMap.get(student);

if (count != null){
   // update value
} else {
   // insert value.
}

The new methods provide a more concise and readable way to perform these operations, and also allow for more flexible and efficient computation. 

In this article, I will show you how you can use compute and computeIfAbsent() method to easily update value in any Map like HashMap and ConcurrentHashMap, but before that let's find out what is compute() and computeIfAbsent() method do and how to use them with examples:


How to use compute() method of ConcurrentHashMap in Java

As I said compute() method in Java is a method of the java.util.Map interface and that's why its available to all the class which implements Map interface like HashMap, LinkedHashMap, TreeMap and ConcurrentHashMap. 

It takes a key as an argument and modifies the value associated with that key using a provided function. The method returns the previous value associated with the key, or null if the key was not present in the map.

The method signature for compute is as follows:

V compute(K key, BiFunction<K, V, V> remappingFunction)

where K is the type of the key, V is the type of the value, and BiFunction<K, V, V> is a java.util.function.BiFunction that takes a key and its associated value, and returns a new value to replace the old one.

Here's an example that demonstrates the use of the compute method:

import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;

public class Main {
  public static void main(String[] args) {
    Map<String, Integer> map = new HashMap<>();
    map.put("A", 1);
    map.put("B", 2);

    BiFunction<String, Integer, Integer> remappingFunction 
     = (key, value) -> value + 1;

    Integer oldValue = map.compute("A", remappingFunction);
    System.out.println("Old value of A: " + oldValue); // 1
    System.out.println("New value of A: " + map.get("A")); // 2

    oldValue = map.compute("C", remappingFunction);
    System.out.println("Old value of C: " + oldValue); // null
    System.out.println("New value of C: " + map.get("C")); // 1
  }
}


In this example, the compute method is used to update the value associated with a key in the map. If the key exists, its value is updated using the provided remappingFunction. If the key does not exist, the method inserts the key with its computed value. 

You can see there is no if-else logic or no null check required as we have to do in first example without using compute() function.


How to update value in ConcurrentHashMap atomically in Java




What is computeIfAbsent() method in Java?

Now that you are familiar with the compute() method, let's see its two cousins or variants, computeIfAbsent() and computeIfPresent()

The computeIfAbsent method takes a key as an argument and returns the value associated with that key if it exists, or computes and inserts the value associated with the key using a provided function if it does not. 

The method returns the value associated with the key, either from the map or the result of computing it.

The method signature for computeIfAbsent is as follows:


V computeIfAbsent(K key, Function<K, V> mappingFunction)

where K is the type of the key and V is the type of the value. The mappingFunction is a java.util.function.Function that takes a key and returns a value to be associated with that key.

Here's an example that demonstrates the use of computeIfAbsent:

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

public class ComputeIfAbsentDemo{
  public static void main(String[] args) {
    Map<String, Integer> map = new HashMap<>();

    Function<String, Integer> mappingFunction = s -> s.length();

    Integer value = map.computeIfAbsent("Hello", mappingFunction);
    System.out.println(value); // 5

    value = map.computeIfAbsent("World", mappingFunction);
    System.out.println(value); // 5
  }
}

In this example, the computeIfAbsent method is used to insert the length of a string as its value in the map if the key does not already exist in the map. If the key exists, the method returns its associated value without computing it again.


ComputeIfAbsent Example in Java using Factorial and ConcurrentHashMap

Here is my complete Java program to demonstrate how to use computeIfAbsent with a real world example of calculating factorial and using ConcurrentHashMap as a cache in Java. 

In this example, I have used ConcurrentHashMap as a cache to store previously calculated factorial values. It's similar to memoization which is used in Dynamic Programming, particularly while calculating Nth Fibonacci number.

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
 
/**
* Java ConcurrentHashMap computeIfAbsent Example
*/
public class ComputeIfAbsentDemo {
 
    static Map<Integer, Long> cache = new ConcurrentHashMap<>();
 
    public static void main(String[] args) throws Exception {
 
        System.out.println("Factorial without Memoization in Java");
        factorial(6);
 
        System.out.println("Factorial with Memoization in Java -"
                + " Using ConcurrentHashmap computeIfAbsent()");
        factorialInJava8(6);
 
        System.out.println("Calculating factorial second time,
                     without Memoization");
        factorial(7);
 
        System.out.println("Calculating factorial second time 
               with memoization");
        factorialInJava8(7);
    }
 
    public static long factorial(int num) {
        if (num == 0) {
            return 1;
        }
        System.out.println("Calculating factorial(" + num + ")");
        return num * factorial(num - 1);
    }
 
    public static long factorialWithMemoization(int number) {
        if (number == 0) {
            return 1;
        }
        Long factorial = cache.get(number);
        if (factorial == null) {
            synchronized (cache) {
                factorial = cache.get(number);
                if (factorial == null) {
                    System.out.println("Calculating factorial of " 
                             + number);
                    factorial = factorial(number - 1);
                    cache.putIfAbsent(number, factorial);
                }
            }
        }
 
        return factorial;
 
    }
 
    public static long factorialInJava8(int input) {
        if (input <= 1) {
            return 1;
        }
        return cache.computeIfAbsent(input, (number) -> {
            System.out.println("Calculating factorial of " + number);
            return input * factorialInJava8(input - 1);
        });
    }
 
}
Output
run:
Factorial without Memoization in Java
Calculating factorial(6)
Calculating factorial(5)
Calculating factorial(4)
Calculating factorial(3)
Calculating factorial(2)
Calculating factorial(1)
Factorial with Memoization in Java - Using ConcurrentHashmap computeIfAbsent()
Calculating factorial of 6
Calculating factorial of 5
Calculating factorial of 4
Calculating factorial of 3
Calculating factorial of 2
Calculating factorial second time, without Memoization
Calculating factorial(7)
Calculating factorial(6)
Calculating factorial(5)
Calculating factorial(4)
Calculating factorial(3)
Calculating factorial(2)
Calculating factorial(1)
Calculating factorial second time with memoization
Calculating factorial of 7


As mentioned before, we’re using the newly added Map.computeIfAbsent() method to calculate a new value from a source function only if we don’t already have a value for a given key. Caching! 

And since this method is guaranteed to execute atomically, and since we’re using a ConcurrentHashMap, this cache is even thread-safe without resorting to manually applying synchronized anywhere. And it can be reused for stuff other than calculating factorial or Fibonacci numbers


ComputeIfPresent() Example in Java

Similar to compute() and computeIfPresent(), computeIfPresent() is a method in Java's Map interface that allows you to update the value of a specified key in the map if the key is present. If the key is not present, the method does nothing. 

This method is often used in conjunction with lambda expressions to define the new value based on the existing value.

Here's an example of how to use computeIfPresent() in Java:

import java.util.HashMap;
import java.util.Map;

public class ComputeIfPresentExample {
    public static void main(String[] args) {
        // Create a HashMap with some key-value pairs
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 100);
        scores.put("Bob", 75);
        scores.put("Charlie", 90);

        System.out.println("Original scores: " + scores);

        // Let's say we want to increase Alice's score by 10 if she exists in the map
        scores.computeIfPresent("Alice", (key, oldValue) -> oldValue + 10);

        // Now, let's try to update the score for Eve, who doesn't exist in the map
        scores.computeIfPresent("Eve", (key, oldValue) -> oldValue + 10);

        System.out.println("Updated scores: " + scores);
    }
}

Ouptut
Original scores: {Alice=100, Bob=75, Charlie=90}
Updated scores: {Alice=110, Bob=75, Charlie=90}


In the example above:

We create a HashMap called scores with initial key-value pairs.

We use computeIfPresent() to update Alice's score by 10. The lambda expression (key, oldValue) -> oldValue + 10 takes the key ("Alice") and the old value (100), and returns the new value (110). Since "Alice" is present in the map, the score is updated.

We then use computeIfPresent() to try to update the score for "Eve," who doesn't exist in the map. Since "Eve" is not present, this computation has no effect on the map.

In short, computeIfPresent() is a useful method for conditionally updating values in a map based on their existing values.

Pros and Cons of compute() and computeIfAbsent() method

Now that you have understood what is compute() method and its variant like computeIfAbsent or computeIfPresent, it's time to see the pros and cons of using them. Here is a list of advantages and disadvantages of using computeIfAbsent, compute, and computeIfPresent methods in Java:

1. Clean and Concise code
These methods provide a more concise and readable way to update values in a map based on some computation, compared to using get and put methods.

2. Atomic
These method update values atomically so you don't need external synchronization. 

3. Improved performance
The methods are designed to be efficient, and they can sometimes perform better than using get and put methods, especially in cases where the computation is expensive or the key is not found in the map.

4. Improved functional programming
These methods make it easier to implement functional programming patterns and to write code that is more expressive, maintainable, and reusable.

So far we have seen all advantages but nothing in this world is free as they say there is no free lunch, so these method also comes with few challenges. Here are common cons of using computeIfAbsent, compute, and computeIfPresent methods in Java:

1. Unfamiliarity
These methods are relatively new and not as widely used as the older get and put methods, so some developers may not be familiar with them.

2. More complex logic
These methods can be more complex to understand and use correctly, especially for developers who are not familiar with functional programming concepts.

3. Performance trade-offs
The performance benefits of these methods depend on the specific use case and implementation, and there may be situations where using get and put methods is more efficient.



That's all about how to use compute, computeIfAbsent, and computeIfPrsent() method in Java. These methods are mainly used to update a value atomically in a Java Map like ConcurrentHashMap. The methods were introduced to provide a more concise and readable way to perform these operations, and also allow for more flexible and efficient computation. 

Additionally, these methods are useful in situations where you need to perform some operation on a value in the map, but only if a key is present or absent, as they provide a more concise way to do that.

While they are great in many ways are also few things which you need to consider while using  computeIfAbsent, compute, and computeIfPresent methods in Java,  and the most important of them is performance implications.  

As with any software design decision, it is important to consider the performance implications of using these methods, and to evaluate whether they are appropriate for your specific use case.

Other Java 8 Tutorial you may like
  • 5 Books to Learn Java 8 from Scratch (books)
  • Top 5 Courses to learn Java 8 Lambda and Stream (courses)
  • How to sort the map by keys in Java 8? (example)
  • How to use filter() method in Java 8 (tutorial)
  • 20 Examples of Date and Time in Java 8 (tutorial)
  • How to use Stream class in Java 8 (tutorial)
  • Difference between abstract class and interface in Java 8? (answer)
  • How to convert List to Map in Java 8 (solution)
  • How to use peek() method in Java 8 (example)
  • How to format/parse the date with LocalDateTime in Java 8? (tutorial)
  • How to sort the may by values in Java 8? (example)
  • How to join String in Java 8 (example)
  • 10 examples of Optionals in Java 8? (example)

Thanks for reading this article so far. If you like this Java 8 compute and computeIfAbsent tutorial and example then please share it with your friends and colleagues. If you have any questions or suggestions then please drop a comment.

P. S. - If you are new to Java 8 features, particularly lambda expression and Stream API, and looking for a free online course to improve your functional programming skills then you can also join Java 8 Functional Programming: Lambda Expressions Quickly (FREE) course on Udemy. It's completely free and more than 27,000 students have already benefited from it

And lastly one question for you, what is difference between compute() and merge() method in Java? When do you suppose to use compute() over merge() and vice-versa?

No comments :

Post a Comment