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