ConcurrentHashMap/ConcurrentMap/Map.compute
Remi Forax
forax at univ-mlv.fr
Wed Dec 5 05:40:42 PST 2012
You can distinguish between the fact that null is stored or not by using
a dedicated functional interface that send if the value is present or
not as parameter. For the return value, you can use a special value for
saying NO_VALUE.
interface MapFunction<K, V> { <-- better name needed
Object NO_VALUE = new Object();
// returns K| NO_VALUE
public Object apply(K key, boolean isPresent, String v);
}
As you see the way to specify the return type is ugly and unsafe.
Now, given that people are used to Map.get() returning null, I think
it's better to have apply returning a K with your proposed semantics.
Rémi
On 12/05/2012 01:39 PM, Doug Lea wrote:
>
> One of the many reasons that this null stuff is driving me crazy is
> that I'm trying to make good on promises to help "elevate"
> the nice lambdaized methods added in ConcurrentHashMap up
> to at least ConcurrentMap and ideally to Map. But I don't
> know how to make plausible specs that say what happens
> with null keys and values.
>
> Pasted below is what I have for an adaptation of the most
> basic one, method Map.compute(). Three other similar methods
> computeIfAbsent(), computeIfPresent(), and merge()
> amount to special cases that are generally simpler to
> use and more efficient when they apply.
>
> After a few stabs at it, I think that the only sane way
> to spec and default-implement it is to say that, even
> if your Map implementation otherwise allows null keys/values,
> that this method will act as if it doesn't. Feel free
> to try fleshing it out yourself under different policies
> and see if you can come up with anything usable and
> humanly decodable. If you can, please let me know.
>
>
> /**
> * Computes a new mapping value given a key and its current mapped
> * value (or {@code null} if there is no current mapping). The
> * default implementation is equivalent to
> *
> * <pre> {@code
> * value = remappingFunction.apply(key, map.get(key));
> * if (value != null)
> * map.put(key, value);
> * else
> * map.remove(key);
> * }</pre>
> *
> * If the function returns {@code null}, the mapping is removed.
> * If the function itself throws an (unchecked) exception, the
> * exception is rethrown to its caller, and the current mapping is
> * left unchanged. For example, to either create or append new
> * messages to a value mapping:
> *
> * <pre> {@code
> * Map<Key, String> map = ...;
> * final String msg = ...;
> * map.compute(key, new BiFunction<Key, String, String>() {
> * public String apply(Key k, String v) {
> * return (v == null) ? msg : v + msg;});}}</pre>
> *
> * <p>The default implementation makes no guarantees about
> * synchronization or atomicity properties of this method. Any
> * class overriding this method must specify its concurrency
> * properties.
> *
> * @param key key with which the specified value is to be associated
> * @param remappingFunction the function to compute a value
> * @return the new value associated with the specified key, or
> null if none
> * @throws NullPointerException if the specified key or
> remappingFunction
> * is null
> * @throws RuntimeException or Error if the remappingFunction does
> so,
> * in which case the mapping is unchanged
> */
> V compute(K key,
> BiFunction<? super K, ? super V, ? extends V>
> remappingFunction);
More information about the lambda-libs-spec-observers
mailing list