Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use the new computeIfAbsent function?

I very much want to use Map.computeIfAbsent but it has been too long since lambdas in undergrad.

Almost directly from the docs: it gives an example of the old way to do things:

Map<String, Boolean> whoLetDogsOut = new ConcurrentHashMap<>();
String key = "snoop";
if (whoLetDogsOut.get(key) == null) {
  Boolean isLetOut = tryToLetOut(key);
  if (isLetOut != null)
    map.putIfAbsent(key, isLetOut);
}

And the new way:

map.computeIfAbsent(key, k -> new Value(f(k)));

But in their example, I think I'm not quite "getting it." How would I transform the code to use the new lambda way of expressing this?

like image 531
Benjamin H Avatar asked Oct 09 '13 17:10

Benjamin H


People also ask

How does computeIfAbsent work?

The Java HashMap computeIfAbsent() method computes a new value and associates it with the specified key if the key is not associated with any value in the hashmap. Here, hashmap is an object of the HashMap class.

Does computeIfAbsent Add to map?

computeIfAbsent() is your new friend for Map in Java Unlike putIfAbsent, here the API returns the existing value or the new value, allowing you to continue working on it more fluently.

Is computeIfAbsent Atomic?

It's important to notice that with computeIfAbsent(), a compound operation is not necessarily atomic because updates are performed outside of the method.


3 Answers

Recently I was playing with this method too. I wrote a memoized algorithm to calcualte Fibonacci numbers which could serve as another illustration on how to use the method.

We can start by defining a map and putting the values in it for the base cases, namely, fibonnaci(0) and fibonacci(1):

private static Map<Integer,Long> memo = new HashMap<>();
static {
   memo.put(0,0L); //fibonacci(0)
   memo.put(1,1L); //fibonacci(1)
}

And for the inductive step all we have to do is redefine our Fibonacci function as follows:

public static long fibonacci(int x) {
   return memo.computeIfAbsent(x, n -> fibonacci(n-2) + fibonacci(n-1));
}

As you can see, the method computeIfAbsent will use the provided lambda expression to calculate the Fibonacci number when the number is not present in the map. This represents a significant improvement over the traditional, tree recursive algorithm.

like image 100
Edwin Dalorzo Avatar answered Oct 25 '22 16:10

Edwin Dalorzo


Suppose you have the following code:

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Test {
    public static void main(String[] s) {
        Map<String, Boolean> whoLetDogsOut = new ConcurrentHashMap<>();
        whoLetDogsOut.computeIfAbsent("snoop", k -> f(k));
        whoLetDogsOut.computeIfAbsent("snoop", k -> f(k));
    }
    static boolean f(String s) {
        System.out.println("creating a value for \""+s+'"');
        return s.isEmpty();
    }
}

Then you will see the message creating a value for "snoop" exactly once as on the second invocation of computeIfAbsent there is already a value for that key. The k in the lambda expression k -> f(k) is just a placeolder (parameter) for the key which the map will pass to your lambda for computing the value. So in the example the key is passed to the function invocation.

Alternatively you could write: whoLetDogsOut.computeIfAbsent("snoop", k -> k.isEmpty()); to achieve the same result without a helper method (but you won’t see the debugging output then). And even simpler, as it is a simple delegation to an existing method you could write: whoLetDogsOut.computeIfAbsent("snoop", String::isEmpty); This delegation does not need any parameters to be written.

To be closer to the example in your question, you could write it as whoLetDogsOut.computeIfAbsent("snoop", key -> tryToLetOut(key)); (it doesn’t matter whether you name the parameter k or key). Or write it as whoLetDogsOut.computeIfAbsent("snoop", MyClass::tryToLetOut); if tryToLetOut is static or whoLetDogsOut.computeIfAbsent("snoop", this::tryToLetOut); if tryToLetOut is an instance method.

like image 25
Holger Avatar answered Oct 25 '22 16:10

Holger


Another example. When building a complex map of maps, the computeIfAbsent() method is a replacement for map's get() method. Through chaining of computeIfAbsent() calls together, missing containers are constructed on-the-fly by provided lambda expressions:

  // Stores regional movie ratings
  Map<String, Map<Integer, Set<String>>> regionalMovieRatings = new TreeMap<>();

  // This will throw NullPointerException!
  regionalMovieRatings.get("New York").get(5).add("Boyhood");

  // This will work
  regionalMovieRatings
    .computeIfAbsent("New York", region -> new TreeMap<>())
    .computeIfAbsent(5, rating -> new TreeSet<>())
    .add("Boyhood");
like image 45
hexabc Avatar answered Oct 25 '22 18:10

hexabc