'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?
Solution 1:[1]
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 2:[2]
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 3:[3]
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:[4]
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:[5]
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);
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | |
Solution 3 | |
Solution 4 | Basil Bourque |
Solution 5 | Nrj |