Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Groovy Map.get(key, default) mutates the map

I have following Groovy script:

mymap = ['key': 'value']
println mymap

v = mymap.get('notexistkey', 'default')

println v
println mymap

When I run it I get following console output:

[key:value]
default
[key:value, notexistkey:default]

I'm surprised that after calling mymap.get('notexistkey', 'default') where second parameter is a default value returned when given key does not exist, the key notexistkey is added to the map I've called the method on. Why? Is that expected behaviour? How can I prevent this mutation?

like image 504
Hugues Fontenelle Avatar asked Jan 03 '23 19:01

Hugues Fontenelle


1 Answers

Use Java's Map.getOrDefault(key, value) instead:

mymap = ['key': 'value']
println mymap

v = mymap.getOrDefault('notexistingkey', 'default')

println v
println mymap

Output:

[key:value]
default
[key:value]

Groovy SDK adds Map.get(key, default) via DefaultGroovyMethods.get(map, key, default) and if you take a look what Javadoc says you will understand that this behaviour is expected:

Looks up an item in a Map for the given key and returns the value - unless there is no entry for the given key in which case add the default value to the map and return that.

And this is what implementation of this method looks like:

/**
 * Looks up an item in a Map for the given key and returns the value - unless
 * there is no entry for the given key in which case add the default value
 * to the map and return that.
 * <pre class="groovyTestCase">def map=[:]
 * map.get("a", []) &lt;&lt; 5
 * assert map == [a:[5]]</pre>
 *
 * @param map          a Map
 * @param key          the key to lookup the value of
 * @param defaultValue the value to return and add to the map for this key if
 *                     there is no entry for the given key
 * @return the value of the given key or the default value, added to the map if the
 *         key did not exist
 * @since 1.0
 */
public static <K, V> V get(Map<K, V> map, K key, V defaultValue) {
    if (!map.containsKey(key)) {
        map.put(key, defaultValue);
    }
    return map.get(key);
}

it's pretty old concept (since Groovy 1.0). However I would recommend not using it - this .get(key, default) operation is neither atomic, nor synchronized. And problems start when you use it on ConcurrentMap which is designed for concurrent access - this method breaks its contract, because there is no synchronization between containsKey, put and final get call.

like image 157
Szymon Stepniak Avatar answered Jan 12 '23 11:01

Szymon Stepniak