How do I use the new computeIfAbsent function?

JavaDictionaryLambdaJava 8

Java Problem Overview


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?

Java Solutions


Solution 1 - Java

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.

Solution 2 - Java

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.

Solution 3 - Java

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");

Solution 4 - Java

multi-map

This is really helpful if you want to create a multimap without resorting to the Google Guava library for its implementation of MultiMap.

For example, suppose you want to store a list of students who enrolled for a particular subject.

The normal solution for this using JDK library is:

Map<String,List<String>> studentListSubjectWise = new TreeMap<>();
List<String>lis = studentListSubjectWise.get("a");
if(lis == null) {
    lis = new ArrayList<>();
}
lis.add("John");

//continue....

Since it have some boilerplate code, people tend to use Guava Mutltimap.

Using Map.computeIfAbsent, we can write in a single line without guava Multimap as follows.

studentListSubjectWise.computeIfAbsent("a", (x -> new ArrayList<>())).add("John");

Stuart Marks & Brian Goetz did a good talk about this https://www.youtube.com/watch?v=9uTVXxJjuco

Solution 5 - Java

Came up with this comparison example (old vs new) which demonstrates both the approaches;

static Map<String, Set<String>> playerSkills = new HashMap<>();
public static void main(String[] args) {
	//desired output
	//player1, cricket, baseball
	//player2, swimming

	//old way
	add("Player1","cricket");
	add("Player2","swimming");
	add("Player1","baseball");
	
	System.out.println(playerSkills);

	//clear
	playerSkills.clear();
	
	//new
	addNew("Player1","cricket");
	addNew("Player2","swimming");
	addNew("Player1","baseball");
	System.out.println(playerSkills);
	
}

private static void add(String name, String skill) {
	Set<String> skills = playerSkills.get(name);
	if(skills==null) {
		skills= new HashSet<>();
		playerSkills.put(name, skills);
	}
	skills.add(skill);
}

private static void addNew(String name, String skill) {
	playerSkills
			.computeIfAbsent(name, set -> new HashSet<>())
			.add(skill);
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBenjamin HView Question on Stackoverflow
Solution 1 - JavaEdwin DalorzoView Answer on Stackoverflow
Solution 2 - JavaHolgerView Answer on Stackoverflow
Solution 3 - JavahexabcView Answer on Stackoverflow
Solution 4 - JavanantitvView Answer on Stackoverflow
Solution 5 - JavaNrjView Answer on Stackoverflow