The most elegant way to write values in a Map that increase, decrease, or recalculate

The most elegant way to write a value in a Map that increases or decreases automatically or re operates a value without calling get() and put()

I believe you can always meet such needs in the process of development: query a key value in the Map, add one if there is one, and put the new key value as 1 if there is no one (or perform other operations on the value).

Usually, most people write like this:

    @Test
    public void test1() {
        HashMap<Integer, Integer> map = new HashMap();
        map.put(1, 1); // {1=1}

        for (int i = 0; i < 3; i++) {
            if (null == map.get(i)) {
                map.put(i, 1);
                continue;
            }
            int oldNum = map.get(i);
            map.put(i, ++oldNum);
        }
        System.out.println(map); // {0=1, 1=2, 2=1}
    }

Although this writing method can be realized, the operation of get() in put() first feels very clumsy.

So is there a more elegant and beautiful way to operate on the values in the map without calling the get() and put() methods of the map?

Let's start today's play: the newly added compute() method in JDK8

First, let's look at the code that uses the comput() method to realize the above requirements:

@Test
    public void test2() {
        HashMap<Integer, Integer> map = new HashMap();
        map.put(1, 1); // {1=1}

        for (int i = 0; i < 3; i++) {
            map.compute(i, (k, v) -> {
                if (v == null) {
                    return 1;
                }
                return ++v;
            });
        }
        System.out.println(map); // {0=1, 1=2, 2=1}
    }

This method reduces two get and one put, and uses a compute() to solve it perfectly. It is also very simple to use. Just fill in the key value we want to query in the first parameter position, rewrite the operation we want to do on value in the second parameter, and the value of return will re assign the key. (note that + + v is not v + +, v + + actually increases v automatically after assigning the original value)

The syntax of the compute() method is:

hashmap.compute(K key, BiFunction remappingFunction)
  • Key - key
  • remappingFunction - remapping function used to recalculate values
  • Return value:
    • If the original value does not exist and the calculated new value is null, null will be returned without operation
    • The original value does not exist, and the calculated new value is not null. Add a new key value pair and return a new value
    • The original value exists and the calculated new value is null. Delete the original value and return the new value
    • The original value exists, and the calculated new value is not null. Re assign the value and return the value recalculated by remappingFunction

compute() source code analysis (jdk_1.8.0_251):

@Override
    public V compute(K key,
                     BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
        if (remappingFunction == null)
            throw new NullPointerException(); // The entered remappingFunction is null. An exception is thrown
        int hash = hash(key); // Calculate hash value
        Node<K,V>[] tab; Node<K,V> first; int n, i;
        int binCount = 0;
        TreeNode<K,V> t = null;
        Node<K,V> old = null;
        // If the current Map needs to be expanded, or the element array is null, or the element array length is 0, initialize the element array
        // tab = resize() reinitializes the element array (it can also be used for capacity expansion)
        if (size > threshold || (tab = table) == null ||
            (n = tab.length) == 0) 
            n = (tab = resize()).length;
        // map is the data structure of array plus linked list or array plus red and black tree, so first is the first element on the linked list. If first is not null, it will go to if
        if ((first = tab[i = (n - 1) & hash]) != null) {
        	// If it is a subclass of TreeNode (red black tree data structure), obtain the original Node through the method
            if (first instanceof TreeNode)
                old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
                
            // Linked list data structure,
            else {
            	// e is used to point to the elements of the linked list
                Node<K,V> e = first; K k;
                do {
                	// If the hash values are equal and the key values are equal (the key is not null and the equals method is true), the original Node is obtained
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k)))) {
                        old = e;
                        break;
                    }
                    // Record the number of elements in the linked list, which is used to judge whether it exceeds the threshold and convert the linked list into a red black tree
                    ++binCount;
                } while ((e = e.next) != null);
            }
        }
        // oldvalue: original value
        V oldValue = (old == null) ? null : old.value;
        // v: Calculate the new value according to the method we implemented
        V v = remappingFunction.apply(key, oldValue);
        if (old != null) {
            if (v != null) {
            	// If the original map has a key value pair corresponding to the key, the calculated new value is used for assignment
                old.value = v;
                // The post-processing after the element is accessed is implemented in LinkedHashMap
                afterNodeAccess(old);
            }
            else
            	// The original map has a key value pair corresponding to the key, but the newly calculated value is null. Delete the original value.
                removeNode(hash, key, null, false, true);
        }
        // New value is not null
        else if (v != null) {
        	// The original map has no key value pair corresponding to the key, and the current bucket is stored in the red black tree data structure. Add this key value pair
            if (t != null)
                t.putTreeVal(this, tab, hash, key, v);
            else {
            // The original map has no key value pair corresponding to the key, and the current bucket is stored in the linked list data structure. Add this key value pair
                tab[i] = newNode(hash, key, v, first);
                // Check whether the set threshold value of linked list to red black tree is reached. If it is reached, the linked list will be converted to red black tree
                if (binCount >= TREEIFY_THRESHOLD - 1)
                    treeifyBin(tab, hash);
            }
            // The number of changes to the hashMap structure is known. Structure modification refers to the modification of changing the mapping number of hashMap or changing its internal structure in other ways (e.g. re hash). This field is used for the rapid failure of the collectionview iterator of hashMap
            ++modCount;
            // hashmap size (logarithm of key value)++
            ++size;
            // The post-processing after adding new elements is implemented in LinkedHashMap
            afterNodeInsertion(true);
        }
        return v;
    }

The merge() method is similar to the comoute() method, but more concise:

Use merge() to implement the above requirements

@Test
    public void test2() {
        HashMap<Integer, Integer> map = new HashMap();
        map.put(1, 1); // {1=1}

         for (int i = 0; i < 3; i++) {
            map.merge(i, 1, (oldvalue, newvalue) -> ++oldvalue);
        }
        System.out.println(map); // {0=1, 1=2, 2=1}
    }

The first parameter is key
The second parameter is to assign a value using this parameter if the value of the key is null or does not exist
The third parameter is the operation on the new value and the old value

Other methods similar to the compute() method:

computeIfPresent(): if there is a key in the map, the old value will be calculated and assigned
computeIfAbsent(): if there is no key in the map, assign a value to the new value after calculation

merge(), computeIfPresent(), computeIfAbsent() can view the hashmap source code by itself, which is similar to the above compute() source code.

So far, we have learned several new methods of hashMap operation added in JDK8. These methods can make it easier for us to operate the key value in the map, save the cumbersome operation of get in put first, and make the code simpler and smoother in the process of development.

If this article is helpful to you, you can pay attention to me! I will continue to update my usual java knowledge!

Keywords: Java Back-end HashMap map

Added by Ward on Sat, 19 Feb 2022 02:50:01 +0200