Improving PutIfAbsent…

Since Java 5 we have ConcurrentMap interface with a couple new methods, one of which is:

public V putIfAbsent(K key, V val);

This essentially replaces the following code:

if (!map.containsKey(key))
    return map.put(key, value);
else
    return map.get(key);

Although this method is useful sometime I found several nagging problems with similar methods:

  1. It doesn’t allow chaining as it returns null if value was actually added, and
  2. It forces me to create value (to pass as a parameter) every time even though it will be ignored all times except for the first time when it’s added.

In GridGain we’ve remedied this problem (setting aside atomicity of the putIfAbsent – our version requires outside synchronization) by having a utility method that, by the way, will be publicly available as part of new functional APIs added to GridGain 3.0:

public static <K, V> V mapGet(@NotNull Map<K, V> map, @Nullable K key, @Nullable GridFactoryClosure<V> c) {
    assert map != null;

    // 'null' values are allowed...
    if (map.containsKey(key) == false) {
        V v = c == null ? null : c.apply();

        map.put(key, v);

        return v;
    }
    else {
        return map.get(key);
    }
}

Now this version have several clear advantages:

  1. It has name mapGet to reflect that it is in fact a getter with an option to handle the key-miss;
  2. It allows for chaining as it always returns whatever value is in the map after this method completes, and
  3. It utilizes factory-closure to avoid unnecessary object creation (see below for example).

Factory-closure is a simple closure that takes no parameters and returns new value every time its apply method is called. The same class that contains this mapGet method (GridLang.java) contains other routines that allow for very terse creation of various factory-closures. So, the final usage of mapGet method may look something like that (using typedefs from GridGain 3.0 as well):

Map<String, List<Integer>> map = ...

// Getting list value from the 'map' with the 'key' (inserting a new empty list if 
// one is not present already) and adding '2010' to the final list.
G.mapGet(map, "key", G.<Integer>newList()).add(2010);

Comparing to the usual boiler-plate code:

Map<String, List<Integer>> map = ...

// Getting list value from the 'map' with the 'key' (inserting a new empty list if 
// one is not present already) and adding '2010' to the final list.
List<Integer> list = map.get("key");

if (list == null) {
    list = new ArrayList<Integer>();

    map.put("key", list);
}

list.add(2010);

That looks pretty neat, type safe and almost “clean” except for the Integer type qualification due to limitations of Java type inferencing. And it is pretty efficient too – since method newList(...) returns a constant static factory-closure allowing to fully avoid any unnecessary object creation.

Enjoy!

7 responses

  1. Your arguments are very strange. (1) The putIfAbsent method is no way an getter but an advanced put method and so it naturally follows put method contract which is returning the previous value – not the new one as in your case. (2) You forget that it is an atomic operation – is your function too? (3) I don’t get the idea of calling the mutator method – mapGet. Do you really thing this is a good idea? Usually people don’t expect get methods to change anything!

  2. I’m not disagreeing (mostly). The current method has its uses – but I constantly missing the one that I describe, specifically with chaining and factory closure.

    My wording wasn’t precise. ‘mapGet’ suits my version better – that’s what I meant.

    Best,
    Nikita.

  3. Your implementation is not thread safe. The whole point of putIfAbsent is to provide fast atomic check-then-add operation so you don’t have to synchronize access manually for highly concurrent applications with multiple threads accessing the map.

    You are comparing apples and oranges.

  4. Although you solution is quite neat, I couldn’t resist the temptation to point out that Google Guava’s MapMaker class offers a very elegant and efficient solution to your original problem. Specifically, please take a look at the MapMaker.makeComputingMap(…) method.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: