See the new thread for the general Javadoc issues. I'll focus on null here.

Given a map {"Key" -> null} I find the notion that putIfAbsent() or
computeIfAbsent() ignore the existing mapping just plain wrong. While
I can rationalise it (just) it is a horrible reworking of the meaning
of "absent".

Similarly, for "computeIfPresent()", my expectation is that the
mapping is present, and that the null should be an input to the
function.

Yes, I get that nulls in collections are a pain, but trying to
redefine the meaning of "absent" is more of a pain. I'm aware that
ConcurrentMap makes things interesting, but these two just look wrong.


So, my rules would be
- if an existing mapping has a null value, then the mapping is
"present" and not "absent" (only affects methods with those words in
their name)
- none of these methods should try to add/update a mapping with a null value
- null returned from a function means remove (or is invalid)
- a null value parameter is invalid
- merge where the existing mapping has a null value should not pass
the null to the function

Stephen



On 26 November 2013 04:32, Mike Duigou <mike.dui...@oracle.com> wrote:
>
> On Nov 24 2013, at 16:31 , David Holmes <david.hol...@oracle.com> wrote:
>
>> Hi Mike,
>>
>> There is still no clear spec for what should happen if the param value is 
>> null.
>
> I feel very uncomfortable the status quo of with null being ignored, used for 
> a sentinel and also as value. The relations between null and values in this 
> method are just too complicated.
>
> Currently:
>
> - The provided value may be either null or non-null. Is null a legal value? 
> It depends upon:
>         - Is there an existing value?
>         - Does the Map allow null values?
>         - Does the function allow null values?
> - Existing null values are treated as absent.
>         - If a null value is passed should we remove this mapping or add it 
> to the map?
>               - null might not be accepted by the map
>               - The associated value would still be regarded as absent for 
> future operations.
> - The function may return null to signal "remove".
>
> In particular I dislike adding a null entry to the map if there is no current 
> mapping (or mapped to null). It seems like it should be invariant whether we 
> end up with an associated value. If null is used to signify "remove" then 
> map.contains(key) will return variable results depending upon the initial 
> state. Having the behaviour vary based upon whether the map allows null 
> values would be even worse.
>
> So I would like to suggest that we require value to be non-null. I have 
> provisionally updated the spec and impls accordingly.
>
>> The parenthesized part is wrong.
>
> I think that's overzealous copying from compute(). I have removed it.
>
>>
>> Also you have changed the method implementation not just the implDoc so the 
>> bug synopsis is somewhat misleading.
>
> I will correct this. More changes were made than I originally expected. New 
> synopsis will be "Map.merge implementations should refuse null value param"
>
> I have updated the webrev.
>
> http://cr.openjdk.java.net/~mduigou/JDK-8029055/1/webrev/
>
> Thanks,
>
> Mike
>
>>
>> Thanks,
>> David
>>
>> On 23/11/2013 10:25 AM, Mike Duigou wrote:
>>> We'll be using https://bugs.openjdk.java.net/browse/JDK-8029055 for this 
>>> issue.
>>>
>>> I've posted a webrev here:
>>>
>>> http://cr.openjdk.java.net/~mduigou/JDK-8029055/0/webrev/
>>>
>>> There is an identical change in ConcurrentMap's merge().
>>>
>>> Mike
>>>
>>> On Nov 22 2013, at 16:01 , Henry Jen <henry....@oracle.com> wrote:
>>>
>>>>
>>>> On 11/21/2013 06:33 PM, David Holmes wrote:
>>>>> On 22/11/2013 5:02 AM, Louis Wasserman wrote:
>>>>>> While I agree that case should be specified, I'm not certain I follow why
>>>>>> that's what's going on here.
>>>>>>
>>>>>> The weird condition is that if oldValue is null, not value; oldValue
>>>>>> is the
>>>>>> old result of map.get(key).  The Javadoc seems not just unspecified, but
>>>>>> actively wrong.  Here's a worked example:
>>>>>>
>>>>>> Map<String, Integer> map = new HashMap<>();
>>>>>> map.merge("foo", 3, Integer::plus);
>>>>>> Integer fooValue = map.get("foo");
>>>>>>
>>>>>> Going through the Javadoc-specified default implementation:
>>>>>>
>>>>>>   V oldValue = map.get(key); // oldValue is null
>>>>>>   V newValue = (oldValue == null) ? value :
>>>>>>                remappingFunction.apply(oldValue, value);
>>>>>>      // newValue is set to value, which is 3
>>>>>>   if (newValue == null) // newValue is nonnull, branch not taken
>>>>>>       map.remove(key);
>>>>>>   else if (oldValue == null) // oldValue is null, branch taken
>>>>>>       map.remove(key); // removes the entry from the map
>>>>>>   else // else if was already triggered, branch not taken
>>>>>>       map.put(key, newValue);
>>>>>>
>>>>
>>>> Seems like a document bug to me, we should fix this @implSpec.
>>>>
>>>> Cheers,
>>>> Henry
>>>>
>>>
>

Reply via email to