如何使用新的 computeIfAbsent 函数?

Posted

技术标签:

【中文标题】如何使用新的 computeIfAbsent 函数?【英文标题】:How do I use the new computeIfAbsent function? 【发布时间】:2013-10-17 04:34:27 【问题描述】:

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

几乎直接来自文档:它提供了一个旧方法的示例:

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 方式来表达这一点?

【问题讨论】:

我不确定您从那里的示例中不明白什么? 什么是“k”?它是一个被定义的变量吗? “新值”怎么样 - 是来自 java 8 的东西,还是代表我需要定义或覆盖的对象? whoLetDogsOut.computeIfAbsent(key, k -> new Boolean(tryToLetOut(k))) 无法编译,所以我遗漏了一些东西...... 究竟什么不能编译?它会产生什么错误? Temp.java:26: 错误:非法开始表达式 whoLetDogsOut.computeIfAbsent(key, k -> new Boolean(tryToLetOut(k))); (指向“>”) 对我来说编译得很好。确保您真正使用 Java 8 编译器。其他 Java 8 功能是否有效? 【参考方案1】:

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

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

【讨论】:

【参考方案2】:

多地图

如果您想创建 multimap 而不借助 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()); 这会在 JDK 中生成一个类型为 Map&lt;T,List&lt;T&gt; 的多映射,恕我直言。 添加到答案中;关于computeIfAbsent的部分从25:30开始【参考方案3】:

另一个例子。在构建复杂的地图地图时,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");

【讨论】:

【参考方案4】:

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

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

【讨论】:

不错,单行转换为动态编程。非常光滑。 如果你先调用 (n-2) 个,你可能会得到更少的递归调用? 递归使用 computeIfAbsent 时要更加小心。更多详情请查看***.com/questions/28840047/… 这段代码导致HashMap的内部被破坏,就像在bugs.openjdk.java.net/browse/JDK-8172951中一样,并且在Java 9中将失败并显示ConcurrentModificationException (bugs.openjdk.java.net/browse/JDK-8071667) 文档字面上说映射函数在计算过程中不应该修改这个映射,所以这个答案显然是错误的。【参考方案5】:

假设你有以下代码:

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

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

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

【讨论】:

以上是关于如何使用新的 computeIfAbsent 函数?的主要内容,如果未能解决你的问题,请参考以下文章

Map补充:map遍历方法和computeIfAbsent()方法

map computeIfAbsent 的浅尝辄止

map computeIfAbsent 的浅尝辄止

java代码之美---Java8 Map中的computeIfAbsent方法

巧用map的computeIfAbsent统计次数

巧用map的computeIfAbsent统计次数