1. Overview
1.概述
Map is a commonly used data structure that contains key-value associations. Java offers a variety of methods for manipulating map entries. Since Java 8, some new members have joined the Map family.
Map 是一种包含键值关联的常用数据结构。Java 提供了多种操作映射项的方法。自 Java 8 起,一些新成员加入了 Map 系列。
putIfAbsent() and computeIfAbsent() are two of them. We frequently used these two methods for adding entries. While they might seem similar at first glance, they have distinct behaviors and use cases.
putIfAbsent() 和 computeIfAbsent() 是其中的两种。我们经常使用这两种方法来添加条目。虽然它们乍看之下可能很相似,但它们的行为和用例却截然不同。
In this tutorial, we’ll discuss the difference between these two methods.
在本教程中,我们将讨论这两种方法的区别。
2. Introduction
2.导言
Before diving into the discussion of the difference, let’s establish some common ground.
在深入讨论两者的区别之前,让我们先建立一些共同点。
Both putIfAbsent() and computeIfAbsent() are methods provided by the Map interface in Java, and they share a common goal: adding a key-value pair to a map if the key is absent. This behavior is particularly useful when we want to prevent overwriting existing entries.
putIfAbsent()和computeIfAbsent()都是 Java 中 Map 接口提供的方法,它们有一个共同的目标:如果键不存在,则将键值对添加到 map 中。当我们希望防止覆盖现有条目时,这种行为尤其有用。
It’s worth noting that the “absent” covers two cases:
值得注意的是, “缺席 “包括两种情况: 1.
- The key doesn’t exist in the map
- The key exists, but the associated value is null
However, the two methods’ behaviors aren’t the same.
但是,这两种方法的行为并不相同。
In this tutorial, we won’t discuss how to use these two methods in general. Instead, we’ll only focus on their differences, and we’ll look at their differences from three perspectives.
在本教程中,我们不会讨论这两种方法的一般使用方法。相反,我们将只关注它们之间的差异,并从三个角度来分析它们的差异。
Also, we’ll use unit test assertions to demonstrate the differences. So next, let’s quickly set up our test examples.
此外,我们还将使用单元测试断言来演示其中的差异。接下来,让我们快速设置测试示例。
3. Preparation
3.准备工作
Since we’ll call putIfAbsent() and computeIfAbsent() to insert entries to a map, let’s first create a HashMap instance for all the tests:
由于我们将调用 putIfAbsent() 和 computeIfAbsent() 向映射插入条目,因此让我们首先为所有测试创建一个 HashMap 实例:
private static final Map<String, String> MY_MAP = new HashMap<>();
@BeforeEach
void resetTheMap() {
MY_MAP.clear();
MY_MAP.put("Key A", "value A");
MY_MAP.put("Key B", "value B");
MY_MAP.put("Key C", "value C");
MY_MAP.put("Key Null", null);
}
As we can see, we also created the resetTheMap() method with the @BeforeEach annotation. This method ensures MY_MAP contains the same key-value pairs for each test.
正如我们所见,我们还使用 @BeforeEach 注解创建了 resetTheMap() 方法。该方法可确保MY_MAP在每个测试中都包含相同的键值对。
If we look at computeIfAbsent()’s signature, we see the method accepts the mappingFunction function to compute the new value:
如果我们查看 computeIfAbsent() 的签名,就会发现该方法接受 mappingFunction 函数来计算新值:
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) { ... }
Therefore, let’s create the Magic class to provide some functions:
因此,让我们创建 Magic 类来提供一些功能:
private Magic magic = new Magic();
The Magic class is pretty straightforward. It only offers two methods: nullFunc() always returns null, and strFunc() returns a not-null string.
Magic 类非常简单。它只提供两个方法:nullFunc() 始终返回 空,而 strFunc() 返回一个非空字符串。
class Magic {
public String nullFunc() {
return null;
}
public String strFunc(String input) {
return input + ": A nice string";
}
}
To be fair, in our tests, all new map values used in both putIfAbsent() and computeIfAbsent() come from magic‘s methods.
公平地说,在我们的测试中,putIfAbsent() 和 computeIfAbsent() 中使用的所有新地图值都来自 magic 的方法。
4. The Return Value When the Key Is Absent
4.键缺失时的返回值
The common part of the two methods is “IfAbsent”. We’ve talked about the definition of “absent”. The first difference is the return values of the two methods in the “absent” case.
这两种方法的共同点是”IfAbsent”。我们已经讨论过 “缺席 “的定义。第一个不同点是这两个方法在 “缺席 “情况下的返回值。
Let’s first look at the putIfAbsent() method:
让我们先看看 putIfAbsent() 方法:
// absent: putting new key -> null
String putResult = MY_MAP.putIfAbsent("new key1", magic.nullFunc());
assertNull(putResult);
// absent: putting new key -> not-null
putResult = MY_MAP.putIfAbsent("new key2", magic.strFunc("new key2"));
assertNull(putResult);
// absent: existing key -> null (original)
putResult = MY_MAP.putIfAbsent("Key Null", magic.strFunc("Key Null"));
assertNull(putResult);
As we can see from the above test, when absent, the putIfAbsent() method always returns null, no matter whether the new value is null or not.
从上面的测试中我们可以看到,当值不存在时,putIfAbsent()方法总是返回 空值,无论新值是否为 空值。
Next, let’s execute the same test with computeIfAbsent() and see what it returns:
接下来,让我们使用 computeIfAbsent() 执行相同的测试,看看会返回什么结果:
// absent: computing new key -> null
String computeResult = MY_MAP.computeIfAbsent("new key1", k -> magic.nullFunc());
assertNull(computeResult);
// absent: computing new key -> not-null
computeResult = MY_MAP.computeIfAbsent("new key2", k -> magic.strFunc(k));
assertEquals("new key2: A nice string", computeResult);
// absent: existing key -> null (original)
computeResult = MY_MAP.computeIfAbsent("Key Null", k -> magic.strFunc(k));
assertEquals("Key Null: A nice string", computeResult);
As the test shows, when absent, the computeIfAbsent() method returns the mappingFunction‘s return value.
正如测试所示,当不存在时,computeIfAbsent() 方法会返回 mappingFunction 的返回值。
5. When the New Value Is null
5.当新值为 null 时
We know HashMap allows null values. So next, let’s try to insert a null value to MY_MAP and see how the two methods behave.
我们知道 HashMap 允许空值。因此,接下来让我们尝试向 MY_MAP 插入一个 null 值,看看这两种方法的表现如何。
First, let’s look at putIfAbsent():
首先,让我们来看看 putIfAbsent():
assertEquals(4, MY_MAP.size()); // initial: 4 entries
MY_MAP.putIfAbsent("new key", magic.nullFunc());
assertEquals(5, MY_MAP.size());
assertTrue(MY_MAP.containsKey("new key")); // new entry has been added to the map
assertNull(MY_MAP.get("new key"));
So, when the key doesn’t exist in the target map, putIfAbsent() will always add a new key-value pair to the map, even if the new value is null.
因此,当目标映射中不存在键时,putIfAbsent() 将始终向映射中添加新的键值对,即使新值为 null. 也是如此。
Now, it’s computeIfAbsent()’s turn:
现在,轮到 computeIfAbsent() 了:
assertEquals(4, MY_MAP.size()); // initial: 4 entries
MY_MAP.computeIfAbsent("new key", k -> magic.nullFunc());
assertEquals(4, MY_MAP.size());
assertFalse(MY_MAP.containsKey("new key")); // <- no new entry added to the map
As we can see, if mappingFunction returns null, computeIfAbsent() refuses to add the key-value pair to the map.
我们可以看到,如果 mappingFunction 返回 空,则 computeIfAbsent() 拒绝将键值对添加到映射中。
6. “compute” Is Lazy, “put” Is Eager
6.”计算“很懒惰,”输入“很急切
We’ve talked about two differences between the two methods. Next, let’s have a look at when the “value” part is provided by a method or function whether the two methods behave in the same way.
我们已经讨论了这两种方法的两个不同点。接下来,让我们看看当”值“部分由方法或函数提供时,这两种方法的行为是否相同。
As usual, let’s look at the putIfAbsent() method first:
像往常一样,让我们先看看 putIfAbsent() 方法:
Magic spyMagic = spy(magic);
// key existent
MY_MAP.putIfAbsent("Key A", spyMagic.strFunc("Key A"));
verify(spyMagic, times(1)).strFunc(anyString());
// key absent
MY_MAP.putIfAbsent("new key", spyMagic.strFunc("new key"));
verify(spyMagic, times(2)).strFunc(anyString());
As we can see in the test above, we used the Mockito spy on the magic object to verify whether the strFunc() method was invoked or not. The test turns out that no matter whether the key exists or not, the strFunc() method is always called.
正如我们在上面的测试中所看到的,我们在 magic 对象上使用了 Mockito spy 来验证 strFunc() 方法是否被调用。测试结果表明,无论键是否存在,strFunc() 方法都会被调用。
Next, let’s see how the computeIfAbsent() method handles mappingFunction:
接下来,让我们看看 computeIfAbsent() 方法是如何处理 mappingFunction 的:</em
Magic spyMagic = spy(magic);
// key existent
MY_MAP.computeIfAbsent("Key A", k -> spyMagic.strFunc(k));
verify(spyMagic, never()).strFunc(anyString()); // the function wasn't called
// key absent
MY_MAP.computeIfAbsent("new key", k -> spyMagic.strFunc(k));
verify(spyMagic, times(1)).strFunc(anyString());
As the test shows, computeIfAbsent() does exactly as its name implies, computing mappingFunction only in the “absent” case.
正如测试所示,computeIfAbsent() 正如其名称所暗示的那样,仅在 “不存在 “的情况下计算 mappingFunction 。 <em
Another everyday use case is when we work with something like Map<String, List<String>>:
另一个日常用例是当我们处理类似 Map<String, List<String>> 的内容时:
- putIfAbsent(aKey, new ArrayList()) – No matter whether “aKey” is absent, a new ArrayList object is created
- computeIfAbsent(aKey, k -> new ArrayList()) – A new ArrayList instance is created only if “aKey” is absent
Therefore, we should use putIfAbsent() when adding a key-value pair directly without any computation if the key is absent. On the other hand, computeIfAbsent() can be used when we need to compute the value and add the key-value pair if the key is absent.
因此,当键值对不存在时,我们应使用 putIfAbsent() 直接添加键值对,而无需进行任何计算。另一方面,当我们需要计算值并在键不存在时添加键值对时,可以使用 computeIfAbsent()。
7. Conclusion
7.结论
In this article, we’ve discussed the differences between putIfAbsent() and computeIfAbsent() through examples. Knowing this difference is crucial for making the right choice in our code.
在本文中,我们通过示例讨论了 putIfAbsent() 和 computeIfAbsent() 之间的区别。了解这两者的区别对于我们在代码中做出正确选择至关重要。
As always, the complete source code for the examples is available over on GitHub.
与往常一样,这些示例的完整源代码可在 GitHub 上获取。