kasper at kav.dk
Mon Oct 3 13:13:57 EDT 2011
On 03-10-2011 15:52, Doug Lea wrote:
> On 10/03/11 09:34, √iktor Ҡlang wrote:
>> I'd love to be able to specify a Comparator in the constructor so that
>> conditional remove doesn't rely on reference-equality or .equals (since
>> oftentimes I cannot overload it)
> One reasonable way to do this interacts with a decision
> I keep flip-flopping on because people make good cases for
> all three of the possibilities: What should the map do when
> either a mapper or remapper function returns null?
> The possibilities are:
> 1. treat it as a user error, so throw NullPointerException
> 2. treat it as a (re)mapping failure, so ignore the outcome
> 3. treat it as a removal
I had a similar discussion with myself a while ago for a library I was
designing. I ended up with 2. (ignoring the outcome) for a couple of reasons
I allow users to recompute all mappings for a map. A.la
computeAll(BinaryMapper<? super K, ? super V, ? extends V> remapper)
which will run through any entry in the map and optionally compute a new
value. Since I rarely need to update all values at the same time it is
common I do not want to do anything with the value.
Although volatile writes are not that expensive. Having to update every
mapping is still some task for large maps.
Of course, one way to do avoid this would be to see if the existing
value is identical to the recomputed value. In which case the mapping
does not need to be updated.
In many situations there is a difference between a no-op and updating an
entry in place.
If you working with something such as the event handlers defined in the
upcoming JSR 107. You only want to notify these event handlers in case
a value changes.
Doing a simple check on identify by a map/cache as I suggested in 1). Is
often not enough since users might not create a new value in the compute
method, but instead update the value in place. For example, changing
some bits in a blob.
Likewise, if you are working in a distributed setting you only want to
send new values to other peers if they actually change.
Although it is difficult to generalize. In case a Remapper returned a
null as the result of a user error. I had the feeling a no-op is
preferred instead of deleting the entry from the map.
I wanted both computeIfPresent and computeIfAbsent to behave identical
with respect to null being returned by a mapper/remapper. So either it
was a user error (1) or it was a no-op (2). Which left 3. out.
Even if there are only few use case for it, returning null from
computeIfAbsent will result in no mapping being established.
Since Remapper/Mapper code is invoked within atomicity control, it is
usually short and simple code. So few errors arise. At least I have yet
to come across one resulting from returning null as a mistake.
In general, I don't like null representing anything but a mistake. But
in my case I had some use cases where the benefits of 2. (ignoring the
outcome) outweighed my reluctance towards using nulls.
More information about the Concurrency-interest