Представляя сегодня метод слияния Map, давайте посмотрим на его мощь.
В JDK API такой метод очень особенный, он очень новый, стоит нашего времени, чтобы его понять, а также рекомендуется применить его к фактическому коду проекта, что должно вам очень помочь.Карта.слияние(). Это, вероятно, самая общая операция в Map. Но это также довольно неясно, и очень немногие используют его.
Введение
merge()
Это можно объяснить следующим образом: он присваивает ключу новое значение (если его не существует) или обновляет существующий ключ заданным значением (UPSERT). Начнем с самого простого примера: подсчета уникальных вхождений слова. До java8 код был очень запутанным, и фактическая реализация фактически потеряла свое существенное значение для дизайна.
var map = new HashMap<String, Integer>();
words.forEach(word -> {
var prev = map.get(word);
if (prev == null) {
map.put(word, 1);
} else {
map.put(word, prev + 1);
}
});
В соответствии с логикой приведенного выше кода, при условии, что задан входной набор, выходные результаты следующие:
var words = List.of("Foo", "Bar", "Foo", "Buzz", "Foo", "Buzz", "Fizz", "Fizz");
//...
{Bar=1, Fizz=2, Foo=3, Buzz=2}
Улучшение V1.
Теперь давайте реконструируем его, главным образом, чтобы удалить часть его логики суждений;
words.forEach(word -> {
map.putIfAbsent(word, 0);
map.put(word, map.get(word) + 1);
});
Такие улучшения могут удовлетворить наши требования к рефакторингу. Конкретное использование putIfAbsent() не будет подробно описано.putIfAbsent
Эта строка кода определенно необходима, иначе логика сообщит об ошибке. И в приведенном ниже коде он появляется сноваput
,get
Это будет странно, давайте перейдем к улучшению дизайна.
Улучшить V2
words.forEach(word -> {
map.putIfAbsent(word, 0);
map.computeIfPresent(word, (w, prev) -> prev + 1);
});
computeIfPresent
Только когдаword
Данное преобразование вызывается только тогда, когда ключ в нем существует. В противном случае он ничего не делает. Мы гарантируем, что ключ существует, инициализируя его нулем, поэтому приращение всегда допустимо. Достаточно ли совершенна эта реализация? Не обязательно, есть и другие идеи по уменьшению лишней инициализации.
words.forEach(word ->
map.compute(word, (w, prev) -> prev != null ? prev + 1 : 1)
);
compute ()
Это какcomputeIfPresent()
, но он будет вызываться вне зависимости от наличия или отсутствия данного ключа. Если значение для ключа не существует, параметр prev имеет значение null. Перемещение простого if в троичное выражение, скрытое в лямбде, также далеко не оптимально. Прежде чем я покажу вам окончательную версию, давайте взглянем на слегка упрощенную реализацию по умолчанию.Map.merge()
Анализ исходного кода.
Улучшить V3
Слияние () исходный код
default V merge(K key, V value, BiFunction<V, V, V> remappingFunction) {
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if (newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
Фрагмент стоит тысячи слов. Всегда читайте исходный код, чтобы открыть для себя Новый Свет,merge()
Относится к обоим случаям. Если данный ключ не существует, он становится put(key, value). Однако, если у ключа уже есть какое-то значение, наша remappingFunction может выбрать способ слияния. Эта функция является идеальной возможностью для описанного выше сценария:
- Просто верните новое значение, чтобы перезаписать старое значение:
(old, new) -> new
- Просто верните старое значение, чтобы сохранить старое значение:
(old, new) -> old
- Объедините их как-нибудь, например:
(old, new) -> old + new
- Даже удалите старое значение:
(old, new) -> null
Как видите, он объединяется () очень распространен. Итак, наша проблема, как использоватьmerge()
Шерстяная ткань? код показывает, как показано ниже:
words.forEach(word ->
map.merge(word, 1, (prev, one) -> prev + one)
);
Вы можете понять это следующим образом: если нет ключа, то инициализированное значение равно 1; в противном случае 1 добавляется к существующему значению. в кодеone
Это постоянная, потому что в нашей сцене по умолчанию всегда плюс 1, а конкретные изменения могут быть переключены по желанию.
Сцены
Давайте представим,
merge()
Это действительно так полезно? Какой может быть его сцена?
например. У вас есть класс операций со счетом
class Operation {
private final String accNo;
private final BigDecimal amount;
}
А также серия операций на разную учетную запись:
operations = List.of(
new Operation("123", new BigDecimal("10")),
new Operation("456", new BigDecimal("1200")),
new Operation("123", new BigDecimal("-4")),
new Operation("123", new BigDecimal("8")),
new Operation("456", new BigDecimal("800")),
new Operation("456", new BigDecimal("-1500")),
new Operation("123", new BigDecimal("2")),
new Operation("123", new BigDecimal("-6.5")),
new Operation("456", new BigDecimal("-600"))
);
Мы хотим рассчитать баланс (общую операционную сумму) для каждой учетной записи. если неmerge()
, становится очень хлопотно:
Map balances = new HashMap<String, BigDecimal>();
operations.forEach(op -> {
var key = op.getAccNo();
balances.putIfAbsent(key, BigDecimal.ZERO);
balances.computeIfPresent(key, (accNo, prev) -> prev.add(op.getAmount()));
});
использоватьmerge
код после
operations.forEach(op ->
balances.merge(op.getAccNo(), op.getAmount(),
(soFar, amount) -> soFar.add(amount))
);
Затем оптимизируйте логику.
operations.forEach(op ->
balances.merge(op.getAccNo(), op.getAmount(), BigDecimal::add)
);
Конечно результат правильный, такой лаконичный код возбуждает? Для каждой операцииadd
в данномamount
данныйaccNo
.
{ 123 = 9.5,456 = - 100 }
ConcurrentHashMap
когда мы распространяемся наConcurrentHashMap
приходи, когдаMap.merge
внешний вид, иConcurrentHashMap
Сочетание очень идеальное. Такие сценарии совместного размещения предназначены для однопоточно-безопасной логики, которая автоматизирует операции вставки или обновления.