ConcurrentHashMap/ConcurrentMap/Map.compute (original) (raw)

David M. Lloyd david.lloyd at redhat.com
Fri Dec 14 07:12:32 PST 2012


On 12/14/2012 08:52 AM, Doug Lea wrote:

Back to this after several diversions... On 12/07/12 11:16, Doug Lea wrote:

Basic idea: defaults for function-accepting Map methods are solely in terms of the 4 CM methods, which are in turn non-atomic for non-CM....

Unfortunately the null-value ambiguity hits yet again when moving from writing specs to writing default implementations. (Have I mentioned lately how terrible it is to allow nulls? :-) The defaults for function-accepting methods must rely not only on these 4 CHM methods, but also on get and/or containsKey. For null-accepting maps, you need the pair of them to default-implement (non-atomically) but for others, you must not use the pair of them (just get) to propagate the property that if putIfAbsent is thread-safe then so is computeIfAbsent. The only way out I see is to even further sacrifice sensibility for null-accepting maps, by saying that the methods are allowed to treat absence and mapping to null identically and that the default implementation does so. Here's computeIfAbsent. Any complaints? /** * If the specified key is not already associated with a value (or * is mapped to {@code null)), attempts to compute its value using * the given mapping function and enters it into the map unless * {@code null}. The default implementation is equivalent to the * following, then returning the current value or {@code null} if * absent: * *

 {@code
* if (map.get(key) == null) { * V newValue = mappingFunction.apply(key); * if (newValue != null) * map.putIfAbsent(key, newValue); * }} * * If the function returns {@code null} no mapping is recorded. If * the function itself throws an (unchecked) exception, the * exception is rethrown to its caller, and no mapping is * recorded. The most common usage is to construct a new object * serving as an initial mapped value or memoized result, as in: * *
 {@code
* map.computeIfAbsent(key, k -> new Value(f(k)));} * *

The default implementation makes no guarantees about

* synchronization or atomicity properties of this method or the * application of the mapping function. Any class overriding this * method must specify its concurrency properties. In particular, * all implementations of subinterface {@link * java.util.concurrent.ConcurrentMap} must document whether the * function is applied once atomically only if the value is not * present. Any class that permits null values must document * whether and how this method distinguishes absence from null * mappings. * * @param key key with which the specified value is to be associated * @param mappingFunction the function to compute a value * @return the current (existing or computed) value associated with * the specified key, or null if the computed value is null * @throws NullPointerException if the specified key is null and * this map does not support null keys, or the * mappingFunction is null * @throws UnsupportedOperationException if the put operation * is not supported by this map * @throws ClassCastException if the class of the specified key or value * prevents it from being stored in this map * @throws RuntimeException or Error if the mappingFunction does so, * in which case the mapping is left unestablished */ default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) { V v, newValue; return ((v = get(key)) == null && (newValue = mappingFunction.apply(key)) != null && (v = putIfAbsent(key, newValue)) == null) ? newValue : v; }

What's wrong with:

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

--



More information about the lambda-libs-spec-experts mailing list