Неверная реализация Oracle ConcurrentHashMap?

Я тестирую ConcurrentHashMap в реализации Oracle Java 8:

ConcurrentMap<String, String> concurrentMap = new ConcurrentHashMap<>();
String result = concurrentMap.computeIfAbsent("A", k -> "B");
System.out.println(result);  // "B"
result = concurrentMap.putIfAbsent("AA", "BB");
System.out.println(result);  // null

Javadoc computeIfAbsent говорит, что

Требования к реализации:

Реализация по умолчанию эквивалентна следующим шагам для этой карты, а затем возвращает текущее значение или значение null, если оно отсутствует:

if (map.get(key) == null) {
    V newValue = mappingFunction.apply(key);
    if (newValue != null)
        return map.putIfAbsent(key, newValue);
}

Он сказал, что возвращает текущее значение или значение null, если оно отсутствует. Так не должно ли оно возвращаться null? Учитывая, что putIfAbsent также возвращает null.

Что мне здесь не хватает?

Ответ 1

Пример кода ConcurrentMap.computeIfAbsent не отражает фактическое намерение, скорее всего, ошибку, вызванную неинтуитивным поведением putIfAbsent, в то время как реализация подчиняется документированному намерению. Об этом сообщается в JDK-8174087  и исправлено в Java 9

Обратите внимание, что контракт Map.computeIfAbsent заключен в

Требования к реализации:

Реализация по умолчанию эквивалентна следующим шагам для этой карты, а затем возвращает текущее значение или значение null, если оно отсутствует:

if (map.get(key) == null) {
    V newValue = mappingFunction.apply(key);
    if (newValue != null)
        map.put(key, newValue);
}

без оператора return. Но ясно сказано

Возврат:

текущее (существующее или вычисленное) значение, связанное с указанным ключом, или значение null, если вычисленное значение равно null

Это документация ConcurrentMap.computeIfAbsent, которая пытается включить аспект concurrency, падая за неинтуитивное поведение putIfAbsent:

Требования к реализации:

Реализация по умолчанию эквивалентна следующим шагам для этой карты, а затем возвращает текущее значение или значение null, если оно отсутствует:

if (map.get(key) == null) {
    V newValue = mappingFunction.apply(key);
    if (newValue != null)
        return map.putIfAbsent(key, newValue);
}

но он все еще говорит

Возврат:

текущее (существующее или вычисленное) значение, связанное с указанным ключом, или значение null, если вычисленное значение равно null

и задокументированное намерение должно иметь приоритет над примером кода. Обратите внимание, что фактическая реализация default ConcurrentMap.computeIfAbsent соответствует документированному намерению:

@Override
default V computeIfAbsent(K key,
        Function<? super K, ? extends V> mappingFunction) {
    Objects.requireNonNull(mappingFunction);
    V v, newValue;
    return ((v = get(key)) == null &&
            (newValue = mappingFunction.apply(key)) != null &&
            (v = putIfAbsent(key, newValue)) == null) ? newValue : v;
}

Итак реализация ConcurrentHashMap.computeIfAbsent соответствует документированному намерению обоих, ConcurrentMap.computeIfAbsent и Map.computeIfAbsent относительно возвращаемого значения и также эквивалентна реализации default, предоставляемой интерфейсами.

Для полноты реализации default Map.computeIfAbsent является

default V computeIfAbsent(K key,
        Function<? super K, ? extends V> mappingFunction) {
    Objects.requireNonNull(mappingFunction);
    V v;
    if ((v = get(key)) == null) {
        V newValue;
        if ((newValue = mappingFunction.apply(key)) != null) {
            put(key, newValue);
            return newValue;
        }
    }

    return v;
}

Ответ 2

Фактический код из javadoc:

 if (map.get(key) == null) {
 V newValue = mappingFunction.apply(key);
     if (newValue != null)
         map.put(key, newValue);  // <-
     }
 }

Как видите, в отмеченной строке нет ключевого слова return.

Раздел "return" также говорит:

Возврат:текущее (существующее или вычисленное) значение, связанное с указанным ключом, или значение null, если вычисленное значение равно null