This article brings you a detailed introduction to Map.merge() (with code). It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.
Today we introduce the merge method of Map, let us take a look at its power.
In the JDK API, such a method is very special, it is very novel, and it is worth our time to understand. It is also recommended that you can apply it to actual project code. You guys should be of great help. Map.merge()). This is probably the most versatile operation on Map. But it's also quite obscure, and few people use it.
Background introduction
merge() can be explained as follows: it assigns a new value to the key (if it does not exist) or updates an existing key with a given value (UPSERT). Let's start with the most basic example: counting unique word occurrences. Before Java 8, the code was very confusing, and the actual implementation had actually lost its essential design meaning.
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); } });
According to the logic of the above code, assuming an input set is given, the output result is as follows;
var words = List.of("Foo", "Bar", "Foo", "Buzz", "Foo", "Buzz", "Fizz", "Fizz"); //... {Bar=1, Fizz=2, Foo=3, Buzz=2}
Improve V1
Now let us reconstruct it, mainly removing Some of its judgment logic;
words.forEach(word -> { map.putIfAbsent(word, 0); map.put(word, map.get(word) + 1); });
Such improvements can meet our reconstruction requirements. The specific usage of putIfAbsent() will not be described in detail. The line of code putIfAbsent is definitely needed, otherwise, the subsequent logic will report an error. In the code below, it is strange that put and get appear again. Let us continue to improve the design.
words.forEach(word -> { map.putIfAbsent(word, 0); map.computeIfPresent(word, (w, prev) -> prev + 1); });
computeIfPresent is to call the given conversion only when the key in the word exists. Otherwise it handles nothing. We ensure the key exists by initializing it to zero, so the increment is always valid. Is this implementation perfect enough? Not necessarily, there are other ideas to reduce additional initialization.
words.forEach(word -> map.compute(word, (w, prev) -> prev != null ? prev + 1 : 1) );
compute () is like computeIfPresent(), but it is called regardless of the presence or absence of the given key. If the value of key does not exist, the prev parameter is null. Moving a simple if to a ternary expression hidden in a lambda is also far from optimal performance. Before I show you the final version, let's take a look at a slightly simplified source code analysis of the default implementation of Map.merge().
Improve V3
merge() source code
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; }
Code snippets are worth a thousand words. You can always discover new lands by reading the source code. merge() is suitable for both situations. If the given key does not exist, it becomes put(key, value). However, if the key already has some values, our remappingFunction can choose to merge. This feature is perfect for the above scenario:
(old, new) -> new
(old, new) -> old
(old, new) -> old new
(old, new) -> null
As you can see, it merge () is very general. So, our question is how to use merge()? The code is as follows:
words.forEach(word -> map.merge(word, 1, (prev, one) -> prev + one) );
You can understand it as follows: if there is no key, then the initialized value is equal to 1; otherwise, 1 is added to the existing value. One in the code is a constant, because in our scenario, the default is always plus 1, and the specific changes can be switched at will.
Scenario
Imagine, is merge() really that easy to use? What can its scenes be?
As an example. You have an account operation class
class Operation { private final String accNo; private final BigDecimal amount; }
and a series of operations for different accounts:
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")) );
We want to calculate the balance (total operating amount) for each account. If merge() is not used, it becomes very troublesome:
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())); });
Use the code after merge
operations.forEach(op -> balances.merge(op.getAccNo(), op.getAmount(), (soFar, amount) -> soFar.add(amount)) );
The logic of optimization.
operations.forEach(op -> balances.merge(op.getAccNo(), op.getAmount(), BigDecimal::add) );
Of course the result is correct, is such a concise code exciting? For each operation, add
is given accNo
at the given amount
.
{ 123 = 9.5,456 = - 100 }
ConcurrentHashMap
When we extend to ConcurrentHashMap, when Map.merge appears, the combination with ConcurrentHashMap is very perfect. This matching scenario is for single-thread-safe logic that automatically performs insert or update operations.
This article has ended here. For more other exciting content, you can pay attention to the Java Tutorial Video column of the PHP Chinese website!
The above is the detailed content of Detailed introduction of Map.merge() (with code). For more information, please follow other related articles on the PHP Chinese website!