Java Map and Subtleties of getOrDefault vs computeIfAbsent

Posted by & filed under , .

EasyRidinDukeI wrote recently about the new niceties in the Map interface that Java 8 brought to light where I’m highlighting in particular 2 new methods: getOrDefault and computeIfAbsent (see my previous post here about it). These provide a cleaner (and as it turns out faster too!) way of retrieving values from a Map instance. However, there is a subtle difference about them when it comes to hitting a key which doesn’t have a value associated in the given Map, and this is why I thought to write this post.

As a reminder, before these 2 methods became available in Java 8, this is the sort of code we used to write in order to deal with the case when our key is not present in the Map (I am using here an instance which maps strings to strings for simplicity of code, but the same applies to any other map):

Map<String, String> map = ...
String value = map.get("some key");
if( value == null ) {
   // do something if key not found ...
}

Typically inside the if() above there are 2 use cases:

  1. use a “default” value if the key not found in the Map — but don’t store this value in the Map
  2. use a “default” value AND store it back in the Map under the given key

It turns out that depending on 1 or 2, it matters a lot if we should use getOrDefault or computeIfAbsent! The crucial difference lies in the JavaDoc for computeIfAbsent which states:

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.

Which means that computeIfAbsent actually stores back the value back in the Map (if not null) — thus being equivalent to this code:

Map<String, String> map = ...
String value = map.get("some key");
if( value == null ) {
   value = "some new string";
   map.put( "some key", value );
}

The following piece of code actually showcases this, first using getOrDefault():

static final String NOT_FOUND = "not found";
Map<String, String> map = ...
String value = map.getOrDefault("some key", NOT_FOUND);
//... do some processing
// now check if our key exists in the Map
System.out.println( map.containsKey("some key") ); // prints "false"

And using computeIfAbsent():

static final String NOT_FOUND = "not found";
Map<String, String> map = ...
String value = map.computeIfAbsent("some key", (k) -> NOT_FOUND);
//... do some processing
// now check if our key exists in the Map
System.out.println( map.containsKey("some key") ); // prints "true"

At first glance they both achieve the same thing: if the value is not in the Map, they both return a constant (NOT_FOUND), however, the side effects are different — one modifies the Map and one doesn’t.

Subtle I’d argue but a rather huge difference.