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

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 * *

 {@code
* value = remappingFunction.apply(key, map.get(key)); * if (value != null) * map.put(key, value); * else * map.remove(key); * } * * 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: * *
 {@code
* Map map = ...; * final String msg = ...; * map.compute(key, new BiFunction() { * public String apply(Key k, String v) { * return (v == null) ? msg : v + msg;});}} * *

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-experts mailing list