145

我非常想使用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 方式来表达这一点?

4

5 回答 5

133

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

我们可以首先定义一个映射并将基本情况的值放入其中,即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 表达式来计算斐波那契数。这代表了对传统的树递归算法的重大改进。

于 2013-10-09T21:36:02.597 回答
116

假设您有以下代码:

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因为该键已经有一个值。klambda 表达式中的k -> f(k)只是映射将传递给您的 lambda 以计算值的键的占位符(参数)。因此,在示例中,密钥被传递给函数调用。

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

为了更接近您问题中的示例,您可以将其写为whoLetDogsOut.computeIfAbsent("snoop", key -> tryToLetOut(key));(无论您命名参数k还是key)。或者把它写成whoLetDogsOut.computeIfAbsent("snoop", MyClass::tryToLetOut);if tryToLetOutisstatic或者whoLetDogsOut.computeIfAbsent("snoop", this::tryToLetOut);if tryToLetOutis 一个实例方法。

于 2013-10-09T18:12:35.883 回答
50

另一个例子。在构建复杂的地图地图时,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");
于 2015-01-28T00:14:32.193 回答
40

多地图

如果您想创建多地图 而不求助于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

于 2017-06-22T08:48:04.677 回答
0

提出了这个比较示例(旧与新),它演示了这两种方法;

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);
}
于 2021-05-05T17:55:33.897 回答