Java8映射中 putIfAbsent 和 computeIfAbsent 的区别是什么?

在阅读一篇有趣的文章时,他们声称这两个函数的区别是:

如果指定的 Key 不是,这两个函数都希望添加一个元素 已在地图上显示。

PutIfAbsent 添加具有指定 Value 的元素,而 ComputeIfAbsent 添加一个元素,该元素具有使用 Key 计算的值。 Http://www.buggybread.com/2014/10/java-8-difference-between-map.html

还有

我们已经看到 putIfAbsent 消除了必须的命令式方法 定义 if 语句,但是如果获取 Java 文章是 really hurting our performance?

To optimise this, we don’t want to fetch the articles until we’re 真的确定我们需要他们ーー也就是说我们需要知道关键是 在取物品之前不在。 Http://www.deadcoderising.com/2017-02-14-java-8-declarative-ways-of-modifying-a-map-using-compute-merge-and-replace/

我还不太明白这两个函数之间有什么区别,您能详细说明一下吗?

114953 次浏览

假设你有一个 Map<String,ValueClass>

map.putIfAbsent("key", new ValueClass());

将创建一个 ValueClass实例,即使“ key”键已经在 Map中。这只会创建一个不必要的实例。

另一方面

map.computeIfAbsent("key", k -> new ValueClass());

只有当“ key”键不在 Map中(或映射到 null值)时,才会创建 ValueClass实例。

因此 computeIfAbsent更有效率。

putIfAbsent相当于:

ValueClass value = new ValueClass();
if (map.get("key") == null) {
map.put("key",value);
}

while computeIfAbsent is equivalent to:

if (map.get("key") == null) {
map.put("key",new ValueClass());
}

这两种方法之间的另一个小区别是,computeIfAbsent不会为缺少的键设置 null值。putIfAbsent会。

通过仔细查看方法签名,您可以理解其中的区别:

  • putIfAbsent获取一个键和值,如果该键在映射中没有值,则将该值放入映射中。
  • computeIfAbsent takes a key and a Function. If there is no value for that key in the map, the function is called to create the value, which is then put in the map.

如果您已经有了值,那么使用 putIfAbsent

如果您还没有这个值,并且创建这个值是一个昂贵的操作(例如,必须在数据库中查找这个值) ,那么使用 computeIfAbsent,以便在 map 已经包含了指定键的值的情况下不需要执行这个昂贵的操作。

区别 # 1

computeIfAbsent takes a mapping function, that is called to obtain the value if the key is missing.

putIfAbsent 直接取值。

如果获取该值代价高昂,那么如果键已经存在,则 putIfAbsent将浪费该值。

一个常见的“昂贵”值是 new ArrayList<>(),用于创建 Map<K, List<V>>时,当键已经存在时创建一个新列表(然后丢弃新列表)会产生不必要的垃圾。


区别 # 2

computeIfAbsent 返回“与指定键关联的当前(现有或计算出的)值,如果计算出的值为空,则返回 null”。

putIfAbsent 返回“与指定键关联的先前值,如果没有对该键进行映射,则返回 null”。

因此,如果键已经存在,它们返回相同的值,但是如果键不存在,则 computeIfAbsent返回计算值,而 putIfAbsent返回 null。


区别 # 3

Both method define "absent" as key missing or existing value is null, but:

如果没有键,computeIfAbsent 将不会输入空值。

如果没有键,即使值为 null,putIfAbsent 也会放置该值。

对于以后对 computeIfAbsentputIfAbsentget的调用,它没有什么不同,但是对于像 getOrDefaultcontainsKey这样的调用,它确实有所不同。

V putIfAbsent(K key, V value)-如果指定的键还没有与值相关联(或映射为 null) ,则尝试使用给定的映射函数计算它的值,并将其输入到这个映射中,除非为 null。

V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)-如果指定的键还没有与值相关联(或映射为 null) ,则尝试使用给定的映射函数计算它的值,并将其输入到这个映射中,除非为 null。

阅读文档可以给出更明显的答案。 Https://docs.oracle.com/javase/8/docs/api/java/util/map.html

也许默认实现可以澄清一点... ..。

default V putIfAbsent​(K key, V value) 对于这个映射,默认实现相当于:

 V v = map.get(key);
if (v == null)
v = map.put(key, value);
return v;

另一方面:

default V computeIfAbsent​(K key,
Function<? super K,? extends V> mappingFunction)

等同于:

if (map.get(key) == null) {
V newValue = mappingFunction.apply(key);
if (newValue != null)
map.put(key, newValue);
}

putIfAbsent()为例:

Map myMap = new HashMap();
myMap.put(1,"ABC");
myMap.put(2,"XYZ");
myMap.put(3,"GHI");
//Output of map till this point is : 1-> "ABC", 2-> "XYZ", 3-> "GHI"
myMap.putIfAbsent(3,"cx");
//Output of map till this point is : 1-> "ABC", 2-> "XYZ", 3-> "GHI"

如果地图上已经没有 3的值,那么 cx就是 3的值。

这个问题已经有答案了。我花了一点时间来理解(“如果 map 已经包含 computeIfAbsent 中指定键的值,则不需要执行昂贵的操作”) 我把我的理解写在这里,希望对其他人有所帮助:

当 map 已经包含指定键的值时,putIfAbsent()的行为仅仅是 put()putIfAbsent()只检查键是否为空。 如果它不为空,那么它返回该值,然后 map 再次获取该值。

但是,在 computeIfAbsent()中,键和值都有空检查。在检查 null 值期间,如果它不是 null,那么映射对象中的现有值 分配给 newValue 并返回。这就是为什么在重用 map 的现有值时不需要再次获取该值的原因。

请参考以下程序:

public class MapTest1 {
public static final String AJAY_DEVGAN = "Ajay Devgn";
public static final String AUTOBIOGRAPHY = "Autobiography";
    

public static void main(String[] args) {
MapTest1 mt = new MapTest1();
mt.testPutCompute();
}


private void testPutCompute() {
Map<String, List<String>> movies = getMovieObject();
System.out.println("\nCalling putIfAbsent method.....");
//System.out.println(movies.get(AJAY_DEVGAN));
//movies.put(AJAY_DEVGAN, getAjayDevgnMovies());
movies.putIfAbsent(AJAY_DEVGAN, getAjayDevgnMovies());
        

System.out.println("\nCalling computeIfAbsent method......");
//System.out.println(movies.get(AUTOBIOGRAPHY));
movies.computeIfAbsent(AUTOBIOGRAPHY, t -> getAutobiographyMovies());
        

}


private Map<String, List<String>> getMovieObject() {
Map<String, List<String>> movies = new HashMap<>();


movies.put(AJAY_DEVGAN, getAjayDevgnMovies());
movies.put(AUTOBIOGRAPHY, getAutobiographyMovies());
        

System.out.println(movies);
return movies;
}


private List<String> getAutobiographyMovies() {
System.out.println("Getting autobiography movies");
List<String> list = new ArrayList<>();
list.add("M.S. Dhoni - The Untold Story");
list.add("Sachin: A Billion Dreams");
return list;
}


private List<String> getAjayDevgnMovies() {
System.out.println("Getting Ajay Devgn Movies");
List<String> ajayDevgnMovies = new ArrayList<>();
ajayDevgnMovies.add("Jigar");
ajayDevgnMovies.add("Pyar To Hona Hi Tha");
return ajayDevgnMovies;
}
}

Refer to following code for putIfAbsent() and computeIfAbsent() from interface Map.class

public interface Map<K,V> {
.....


default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}


return v;
}
    

default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}


return v;
}
.........
}