Home > Java > javaTutorial > Detailed introduction of Map.merge() (with code)

Detailed introduction of Map.merge() (with code)

不言
Release: 2019-03-22 17:04:09
forward
6041 people have browsed it

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);
    }
});
Copy after login

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}
Copy after login

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);
});
Copy after login

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.

Improve V2

words.forEach(word -> {
    map.putIfAbsent(word, 0);
    map.computeIfPresent(word, (w, prev) -> prev + 1);
});
Copy after login

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)
);
Copy after login

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;
}
Copy after login

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:

  • Just return the new value to overwrite the old value: (old, new) -> new
  • Just return the old value to keep the old value: (old, new) -> old
  • merge the two in some way, like: (old, new) -> old new
  • Even delete the old value: (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)
);
Copy after login

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;
}
Copy after login

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"))
);
Copy after login

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()));
});
Copy after login

Use the code after merge

operations.forEach(op ->
        balances.merge(op.getAccNo(), op.getAmount(), 
                (soFar, amount) -> soFar.add(amount))
);
Copy after login

The logic of optimization.

operations.forEach(op ->
        balances.merge(op.getAccNo(), op.getAmount(), BigDecimal::add)
);
Copy after login

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 }
Copy after login

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!

Related labels:
source:segmentfault.com
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template