如何使用新的 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<T,List<T>
的多映射,恕我直言。
添加到答案中;关于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 -> f(k)
中的 k
只是映射将传递给您的 lambda 以计算值的键的占位符(参数)。因此,在示例中,密钥被传递给函数调用。
或者,您可以编写:whoLetDogsOut.computeIfAbsent("snoop", k -> k.isEmpty());
以在不使用辅助方法的情况下获得相同的结果(但此时您将看不到调试输出)。甚至更简单,因为它是对现有方法的简单委托,您可以编写:whoLetDogsOut.computeIfAbsent("snoop", String::isEmpty);
此委托不需要编写任何参数。
为了更接近您问题中的示例,您可以将其写为whoLetDogsOut.computeIfAbsent("snoop", key -> tryToLetOut(key));
(将参数命名为k
或key
都没有关系)。或者如果tryToLetOut
是static
或whoLetDogsOut.computeIfAbsent("snoop", this::tryToLetOut);
如果tryToLetOut
是实例方法,则将其写为whoLetDogsOut.computeIfAbsent("snoop", MyClass::tryToLetOut);
。
【讨论】:
以上是关于如何使用新的 computeIfAbsent 函数?的主要内容,如果未能解决你的问题,请参考以下文章
Map补充:map遍历方法和computeIfAbsent()方法