Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Introduction

In this tutorial, we’ll look at ways of copying one HashMap onto another without replacing the keys and values of the target HashMap. A HashMap in Java is a Hash table implementation of the Map interface and is a data structure that supports storing key-value pairs.

2. Problem Statement

Consider that we have two HashMaps, sourceMap and targetMap, containing countries and their capital cities as keys and values. We want to copy the contents of the sourceMap into the targetMap so that we only have one map with all the countries and their capital cities. The copying should adhere to these rules:

  • We should preserve the original contents of the targetMap
  • In case of a collision of a key, for example, a city that exists in both maps, we should preserve the entry of the targetMap

Let’s take the following input:

Map<String, String> sourceMap = new HashMap<>();
sourceMap.put("India", "Delhi");
sourceMap.put("United States", "Washington D.C.");
sourceMap.put("United Kingdom", "London D.C.");

Map<String, String> targetMap = new HashMap<>();
targetMap.put("Zimbabwe", "Harare");
targetMap.put("Norway", "Oslo");
targetMap.put("United Kingdom", "London");

The modified targetMap retains its values and adds all the values of sourceMap:

"India", "Delhi"
"United States", "Washington D.C."
"United Kingdom", "London"
"Zimbabwe", "Harare"
"Norway", "Oslo"

3. Iterating Through the HashMaps

A simple approach to solving our problem would be to iterate through each entry(key-value pair) of the sourceMap and compare it with that of the targetMap. When we find an entry that exists only in sourceMap, we add it to the targetMap. The resultant targetMap contains all the key-values of itself and the sourceMap.

Instead of looping through the entrySets() of both maps, we can loop over the sourceMap‘s entrySet() and check for the existence of the key in targetMap:

Map<String, String> copyByIteration(Map<String, String> sourceMap, Map<String, String> targetMap) {
    for (Map.Entry<String, String> entry : sourceMap.entrySet()) {
        if (!targetMap.containsKey(entry.getKey())) {
            targetMap.put(entry.getKey(), entry.getValue());
        }
    }
    return targetMap;
}

4. Using Map‘s putIfAbsent() 

We can refactor the above code to use the putIfAbsent() method added in Java 8. The method, as its name implies, copies an entry of the sourceMap to the targetMap only if the key in the specified entry is absent:

Map<String, String> copyUsingPutIfAbsent(Map<String, String> sourceMap, Map<String, String> targetMap) {
    for (Map.Entry<String, String> entry : sourceMap.entrySet()) {
        targetMap.putIfAbsent(entry.getKey(), entry.getValue());
    }
    return targetMap;
}

An alternative to using the loop is to utilize the forEach construct added in Java 8. We provide an action, which in our case is to call the putIfAbsent() method on the targetMap input, that it performs for each entry of the given HashMap until all elements have been processed or an exception is raised:

Map<String, String> copyUsingPutIfAbsentForEach(Map<String, String> sourceMap, Map<String, String> targetMap) {
    sourceMap.forEach(targetMap::putIfAbsent);
    return targetMap;
}

5. Using Map‘s putAll()

The Map interface provides a method putAll() which we can use to achieve our desired result. The method copies all keys and values of the input map into the current map. We should note here that in case of a collision of keys between the source and target hashmaps, the entry from the source replaces the targetMap‘s entry.

We can work around this by an explicit removal of the common keys from the sourceMap:

Map<String, String> copyUsingPutAll(Map<String, String> sourceMap, Map<String, String> targetMap) {
    sourceMap.keySet().removeAll(targetMap.keySet());
    targetMap.putAll(sourceMap);
    return targetMap;
}

6. Using merge() on Maps

Java 8 introduced a merge() method in the Maps interface. It takes a key, a value, and a remapping functioning as method parameters.

Suppose the key we specified in the input is not already associated with a value (or is associated with null) in the current map. In that case, the method associates it with the provided non-null value.

If the key is present in both maps, the associated value is replaced with the results of the given remapping function. If the result of the remapping function is null, it removes the key-value pair.

We can use the merge() method for copying over entries from sourceMap to targetMap:

Map<String, String> copyUsingMapMerge(Map<String, String> sourceMap, Map<String, String> targetMap) {
    sourceMap.forEach((key, value) -> targetMap.merge(key, value, (oldVal, newVal) -> oldVal));
    return targetMap;
}

Our remapping function ensures that it preserves the value in the targetMap in case of collision.

7. Using Guava’s Maps.difference()

The Guava library uses a difference() method in its Maps class. To use the Guava library, we should add the corresponding dependency in our pom.xml:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>

The difference() method takes in two maps as input and computes the difference between these two maps. The keys of the supplied maps should honor the equals() and hashCode() contracts.

To solve our problem, we first evaluate the difference between the maps. Once we know the entries which exist only in sourceMap(map on the left), we put them into our targetMap:

Map<String, String> copyUsingGuavaMapDifference(Map<String, String> sourceMap, Map<String, String> targetMap) {
    MapDifference<String, String> differenceMap = Maps.difference(sourceMap, targetMap);
    targetMap.putAll(differenceMap.entriesOnlyOnLeft());
    return targetMap;
}

8. Conclusion

In this article, we looked at different ways we can copy the entries from one HashMap to another while preserving the existing entries of the target HashMap. We implemented an iteration-based approach and solved the problem using different Java library functions. We also looked at how we could solve the problem using the Guava library.

As usual, all code samples can be found over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.