可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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?
回答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.
回答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.
回答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");
回答4:
This is really helpful if you want to create a Multimap without using guava library (https://google.github.io/guava/releases/19.0/api/docs/com/google/common/collect/Multimap.html)
For eg: If 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 boiler plate 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
回答5:
There is no difference between using computeIfAbsent() and simple put() get()
functions of a map. In other words you can rewrite your function in this way
for (char ch : input){ Integer value; if(countMap.containsKey(ch)){ value = countMap.get(ch); value++; countMap.put(ch, value); } else{ value = 1; countMap.put(ch, value); } }