ConcurrentHashMap JDK 8 когда использовать computeIfPresent

В новой версии Concurrent Hash Map для jdk 8 есть два новых метода.

computeIfAbsent

computeIfPresent

putIfAbsent - Старый метод

Я понимаю варианты использования putIfAbsent и computeIfAbsent. Но я не уверен в сценариях, когда буду использовать computeIfPresent. Также почему мне нужно putIfAbsent, если у меня есть computeIfPresent сейчас. putIfAbsent действительно создает по крайней мере один дополнительный экземпляр значения.

Является ли причина только для обратной совместимости?

Ответ 1

Как уже упоминалось в другом ответе: методы всегда будут сохраняться для обратной совместимости, даже если появились новые, более "мощные" методы.

Относительно варианта использования для computeIfPresent: может быть трудно найти пример, который достаточно мал, чтобы не выглядеть надуманным и все еще быть убедительным. В общем, целью этого метода является обновление существующего значения в любой форме.

Одним из примеров может быть (ограниченное) количество слов. Для данного набора слов хранится начальный счет 0 на карте. Затем обрабатывается последовательность слов: всякий раз, когда вы находите слово из исходного набора, его счет увеличивается на 1:

import java.util.LinkedHashMap;
import java.util.Map;

public class ComputeIfPresentExample 
{
    public static void main(String[] args) 
    {
        Map<String, Integer> wordCounts = new LinkedHashMap<String, Integer>();

        String s = 
            "Lorem ipsum dolor sit amet consetetur iam nonumy sadipscing " + 
            "elitr, sed diam nonumy eirmod tempor invidunt ut erat sed " + 
            "labore et dolore magna dolor sit amet aliquyam erat sed diam";

        wordCounts.put("sed", 0);
        wordCounts.put("erat", 0);

        for (String t : s.split(" "))
        {
            wordCounts.computeIfPresent(t, (k,v) -> v+1);
        }
        System.out.println(wordCounts);
    }
}

(Конечно, такие вещи могут быть решены по-разному, но это довольно частая задача в той или иной форме, и новый метод позволяет довольно сжатое и элегантное решение)

Ответ 2

Распространенным вариантом использования являются карты с коллекциями, например

Map<String, Collection<String>> strings = new HashMap<>();

computeIfAbsent и computeIfPresent - очень удобные операции для добавления и удаления элементов в/из коллекции. Вот пример, который группирует строки по их первому символу. Обратите внимание, что ключи и коллекции создаются при необходимости, и очищаются, когда коллекция становится пустой:

void addString(String a) {
    String index = a.substring(0, 1);
    strings.computeIfAbsent(index, ign -> new HashSet<>()).add(a);
}

void removeString(String a) {
    String index = a.substring(0, 1);
    strings.computeIfPresent(index, (k, c) -> {
        c.remove(a);
        return c.isEmpty() ? null : c;
    });
}

Пример:

                         // {}
addString("a1");         // {a=[a1]}      <-- collection dynamically created
addString("a2");         // {a=[a1, a2]}
removeString("a1");      // {a=[a2]}
removeString("a2");      // {}            <-- both key and collection removed

Это чрезвычайно эффективно в многопоточных средах, поскольку ConcurrentMaps выполняют эти операции атомарно.

Операция удаления может быть однострочной:

void removeString(String a) {
    String index = a.substring(0, 1);
    strings.computeIfPresent(index, (i, c) -> c.remove(a) && c.isEmpty() ? null : c);
}

Ответ 3

JDK вряд ли когда-либо нарушает обратную совместимость. Потому что тогда вы не можете легко переносить или запускать программное обеспечение из старой версии с последней версией.

Вы можете запускать программное обеспечение, скомпилированное с более старой версией библиотеки, с любой версией (что означает, что у пользователей установлена ​​JRE), которые все еще имеют эти функции.

Ответ 4

Я использовал computeIfPresent как нулевой способ для извлечения строчных значений из карты строк.

String s = fields.computeIfPresent("foo", (k,v) -> v.toLowerCase())

Прежде чем computeIfPresent был доступен, я должен был бы сделать это:

String s = map.get("foo");
if (s != null) {
    s = s.toLowerCase();
}

Или это:

String s = map.containsKey("foo") ? map.get("foo").toLowerCase() : null;