140

Reading an interesting articles the guys claims that the difference between the two function are:

Both functions aspire to add an element if the specified Key is not already present in Map.

putIfAbsent adds an element with the specified Value whereas computeIfAbsent adds an element with the value computed using the Key. http://www.buggybread.com/2014/10/java-8-difference-between-map.html

And

We’ve seen that putIfAbsent removes the imperative way of having to define the if-statement, but what if fetching the Java articles is really hurting our performance?

To optimise this, we don’t want to fetch the articles until we’re really sure we need them — meaning we need to know if the key is absent before fetching the articles. http://www.deadcoderising.com/2017-02-14-java-8-declarative-ways-of-modifying-a-map-using-compute-merge-and-replace/

I didn't ready understand what are the differences can you please elaborate more on these two functions ?

Adelin
  • 18,144
  • 26
  • 115
  • 175
  • 1
    To have or to have not. If you have the value you can put it; if you don't have it you have to compute and then put it. – laune Jan 10 '18 at 09:25
  • 1
    see this https://stackoverflow.com/questions/10743622/concurrenthashmap-avoid-extra-object-creation-with-putifabsent, looks like a duplicate also – Eugene Jan 10 '18 at 09:29

7 Answers7

246

Difference #1

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

putIfAbsent takes the value directly.

If the value is expensive to obtain, then putIfAbsent wastes that if the key already exists.

A common "expensive" value is e.g. new ArrayList<>() for when you're creating a Map<K, List<V>>, where creating a new list when the key already exists (which then discards the new list) generates unnecessary garbage.


Difference #2

computeIfAbsent returns "the current (existing or computed) value associated with the specified key, or null if the computed value is null".

putIfAbsent returns "the previous value associated with the specified key, or null if there was no mapping for the key".

So, if the key already exists, they return the same thing, but if the key is missing, computeIfAbsent returns the computed value, while putIfAbsent return null.


Difference #3

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

computeIfAbsent will not put a null value if the key is absent.

putIfAbsent will put the value if the key is absent, even if the value is null.

It makes no difference for future calls to computeIfAbsent, putIfAbsent, and get calls, but it does make a difference to calls like getOrDefault and containsKey.

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • 1
    #2 difference caused some NPEs because I was trying `return map.putIfAbsent("key", someMethodReturningNotNullObj);` inside a `fn` function and I was wondering why the hell I was getting nulls from my `fn` function. `computeIfAbsent` is the way! – Silviu Burcea Sep 26 '19 at 16:49
  • 1
    Returning the old value makes putIfAbsent() consistent with put(), which also returns the old value. – reggoodwin Nov 15 '20 at 17:38
  • @reggoodwin `putIfAbsent` is not consistent with `put`, since `putIfAbsent` leaves an existing value unchanged, while `put` replaces the value, hence the "if absent" part of the method name. – Andreas Nov 15 '20 at 17:45
  • 2
    Hello, I guess what I meant was that both methods never return the new value passed into them. Thank you for your answer. – reggoodwin Nov 15 '20 at 21:07
118

Suppose you have a Map<String,ValueClass>.

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

will create a ValueClass instance anyway, even if the "key" key is already in the Map. This would just create an unnecessary instance.

On the other hand

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

will only create a ValueClass instance if the "key" key is not already in the Map (or is mapped to a null value).

Therefore computeIfAbsent is more efficient.

putIfAbsent is equivalent to:

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

Another small difference between the two methods is that computeIfAbsent will not put a null value for an absent key. putIfAbsent will.

Eran
  • 387,369
  • 54
  • 702
  • 768
  • 8
    One fairly frequent use for computeIfAbsent would be a `Map>` where you have to create a List on first encounter of a key value. Originally one did a get, test, new, put... Now it's just the `computeIfAbsent(..., new ArrayList())` – laune Jan 10 '18 at 09:31
15

You can understand the difference by carefully looking at the method signatures:

  • putIfAbsent takes a key and value, and puts the value in the map if there is no value for that key in the map.
  • 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.

If you already have the value, use putIfAbsent.

If you don't have the value yet and creating the value is an expensive operation (for example, the value has to be looked up in a database), then use computeIfAbsent, so that the expensive operation doesn't need to be performed in case the map already contains a value for the specified key.

Jesper
  • 202,709
  • 46
  • 318
  • 350
10

Maybe the default implementations can clarify a little bit more....

default V putIfAbsent​(K key, V value) The default implementation is equivalent to, for this map:

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

On the other hand:

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

is equivalent to:

if (map.get(key) == null) {
     V newValue = mappingFunction.apply(key);
     if (newValue != null)
         map.put(key, newValue);
}
zlakad
  • 1,314
  • 1
  • 9
  • 16
2

V putIfAbsent(K key, V value) - If the specified key is not already associated with a value (or is mapped to null), attempts to compute its value using the given mapping function and enters it into this map unless null.

V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) - If the specified key is not already associated with a value (or is mapped to null), attempts to compute its value using the given mapping function and enters it into this map unless null.

Reading documentation can give you more obvious answer. https://docs.oracle.com/javase/8/docs/api/java/util/Map.html

minotna
  • 167
  • 7
2

This question has already been answered. I took a little time to understand ("expensive operation doesn't need to be performed in case the map already contains a value for the specified key in computeIfAbsent") I am putting my understanding here. Hope this will be helpful to others:

putIfAbsent() behaves simply as put() when map already contains the value for the specified key. putIfAbsent() only checks whether key is null or not. If it is not null then it returns the value and then map fetch that value again.

However, in computeIfAbsent() there is null check for both key and value. During null check for value, if it is not null then existing value from the map object is assigned to newValue and it is returned. That's why there is no need to again fetch the value as existing value from map is re-used.

Refer to following program for reference:

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;
    }   
.........
}
Wolfson
  • 1,187
  • 17
  • 22
S Kumar
  • 555
  • 7
  • 21
-3

Take this simple Example for 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"

cx will be value of 3 if there is no Value of 3 in the map already.

Wolfson
  • 1,187
  • 17
  • 22
Jain
  • 201
  • 2
  • 2
  • the question was about the difference between `putIfAbsent()` and `computeIfAbsent`, not just about what `putIfAbsent()` does. – Wolfson Sep 04 '20 at 09:00
  • I think you mix up `key` and `value` here. See [`Class HashMap`](https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html). – Wolfson Sep 04 '20 at 09:01