I took a quick look, but jumped back in horror at the start of the
Javadoc for the new methods in Map. A Javadoc description should start
with the positive, not the negative. I had to read the code to figure
out what they are supposed to do. Here are the four poor Javadocs and
some proposed enhacements:

merge()
"If the specified key is not already associated with ..."
should be
"Updates the value for an existing key merging the existing value with
the specified value."
"<p>"
"....more detailed user-level explanation..."
"<p>"
"....now the more spec/edge case part..."
("if" is a terrible way to start the docs. Say what it does!)


computeIfAbsent()
"If the specified key is not already associated with a value (or is mapped..."
should be
"Associates the key with a value computed on demand if the key is not
currently present."
"<p>"
"....more detailed user-level explanation..."
"<p>"
"....now the more spec/edge case part..."


computeIfPresent()
"If the value for the specified key is present and non-null,"
should be
"Updates the value for an existing key."
"<p>"
"....more detailed user-level explanation..."
"<p>"
"....now the more spec/edge case part..."


compute()
"Attempts to compute a mapping for the specified key..."
should be
"Updates the value for a key, adding a new mapping if necessary."
"<p>"
"....more detailed user-level explanation..."
"<p>"
"....now the more spec/edge case part..."
(attempts is negative, not positive)

Similar problems seem to exist for "putIfAbsent".

also...

I have to say that I don't overly like the method name.
"map.merge(...)" sounds like a bulk operation affecting the whole map
(ie. like putAll). Assuming that the method cannot be renamed, it
could be improved just by changing the method parameter names.:

default V merge(K key, V valueToMerge,
    BiFunction<? super V, ? super V, ? extends V> mergeFunction) {

"valueToMerge" implies that action will occur to the parameter.
"mergeFunction" is much more meaningful that "remapping Function".

Similar parameter name change, "mappingFunction" -> "valueCreationFunction"
default V computeIfAbsent(
  K key,
  Function<? super K, ? extends V> valueCreationFunction) {

Similar parameter name change, "remappingFunction" -> "valueUpdateFunction"
default V computeIfPresent(
  K key,
  BiFunction<? super K, ? super V, ? extends V> valueUpdateFunction) {

Similar parameter name change, "remappingFunction" -> "valueUpdateFunction"
default V compute(
  K key,
  BiFunction<? super K, ? super V, ? extends V> valueUpdateFunction) {


also...
Can you use HashSet::new in the example code?


In general, all these new methods are written in a spec style, rather
than a user friendly style, and I'm afraid I don't think thats
sufficient.
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