如何使用新的computeIfAbsent函数?


115

我非常想使用Map.computeIfAbsent,但是自从lambdas进入本科以来已经太久了。

几乎直接来自文档:它给出了做事的旧方法的示例:

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

和新方法:

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

但在他们的示例中,我认为我不太“了解”。我将如何转换代码以使用新的lambda表达方式?


我不确定您对那里的示例不了解吗?
Louis Wasserman

2
什么是“ k”?是否正在定义变量?“新值”怎么样-来自Java 8的东西,还是代表我需要定义或覆盖的对象?whoLetDogsOut.computeIfAbsent(key,k-> new Boolean(tryToLetOut(k)))无法编译,所以我错过了一些东西……
Benjamin H

到底什么不编译?它会产生什么错误?
axtavt

Temp.java:26:错误:表达式的非法开头whoLetDogsOut.computeIfAbsent(key,k-> new Boolean(tryToLetOut(k))); (指向“>”)
本杰明·H

编译对我来说很好。确保您确实使用Java 8编译器。其他Java 8功能还能工作吗?
axtavt

Answers:


97

假设您有以下代码:

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();
    }
}

然后,您将看到消息creating a value for "snoop"恰好一次,就像在第二次调用时computeIfAbsent已经存在该键的值一样。的k在λ表达式k -> f(k)仅仅是该地图将传递到您的拉姆达用于计算值的键一个placeolder(参数)。因此,在示例中,键被传递给函数调用。

或者,您可以编写:whoLetDogsOut.computeIfAbsent("snoop", k -> k.isEmpty());在没有辅助方法的情况下获得相同的结果(但是您将看不到调试输出)。甚至更简单,因为它是对现有方法的简单委托,因此您可以编写:whoLetDogsOut.computeIfAbsent("snoop", String::isEmpty);此委托不需要编写任何参数。

为了更接近问题中的示例,您可以将其写为whoLetDogsOut.computeIfAbsent("snoop", key -> tryToLetOut(key));(命名参数k还是都没有关系key)。或将其编写为whoLetDogsOut.computeIfAbsent("snoop", MyClass::tryToLetOut);好像tryToLetOutstaticwhoLetDogsOut.computeIfAbsent("snoop", this::tryToLetOut);如果tryToLetOut是实例方法。


114

最近我也在玩这种方法。我写了一个记忆算法来计算斐波那契数,这可以作为如何使用该方法的另一个例证。

我们可以先定义一个映射,然后将基本情况(即fibonnaci(0)和)中的值放入其中fibonacci(1)

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

对于归纳步​​骤,我们要做的就是重新定义斐波那契函数,如下所示:

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

如您所见,computeIfAbsent当地图中不存在该数字时,该方法将使用提供的lambda表达式计算斐波那契数。这代表了对传统树递归算法的重大改进。


18
尼斯,单行转换为动态编程。很好吃
本杰明·H

3
如果您首先拥有(n-2)个调用,您得到的递归调用可能会更少?
托尔比约恩Ravn的安徒生

9
递归使用computeIfAbsent时,您应该更加谨慎。欲了解更多详情请stackoverflow.com/questions/28840047/...
阿吉特·库马尔·

11
在该代码产生HashMap的内部被损坏,就如同在bugs.openjdk.java.net/browse/JDK-8172951和将失败,并ConcurrentModificationException在Java中9(bugs.openjdk.java.net/browse/JDK-8071667
彼得· Findeisen

22
文档从字面上说,映射函数不应在计算过程中修改此映射,因此此答案显然是错误的。
fps

41

另一个例子。在构建复杂的地图时,computeIfAbsent()方法替代了地图的get()方法。通过将computeIfAbsent()调用链接在一起,可以通过提供的lambda表达式即时构建丢失的容器:

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

31

多图

如果您想创建多图 而不用Google Guava库实现,这将非常有用MultiMap

例如,假设您要存储一个特定学科的注册学生列表。

使用JDK库的常规解决方案是:

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

//continue....

由于它具有一些样板代码,因此人们倾向于使用Guava Mutltimap

使用Map.computeIfAbsent,我们可以在没有guava Multimap的情况下写成一行,如下所示。

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

斯图尔特·马克(Stuart Marks)和布莱恩·格茨(Brian Goetz)很好地谈论了这个 https://www.youtube.com/watch?v=9uTVXxJjuco


在Java 8中(更简洁)studentListSubjectWise.stream().collect(Collectors.GroupingBy(subj::getSubjName, Collectors.toList());制作多图的另一种方法是这样做。这只会Map<T,List<T>在JDK中更简洁地生成imho 类型的多图。
僵尸
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.