Wednesday, May 10, 2023

How to Merge two HashMap in Java 8 - Map.merge() example Tutorial

Hello guys, if you are wondering how to merge two Map like two HashMap in Java then you have come to the right place. In this tutorial,  I will show you step by step to merge two HashMap in Java. You can merge two Map in Java using the newly added merge() function in Java 8. This allows you to copy values from one map to another to merge both of them. It also provide a lot of flexibility to handle duplicate keys. In the last article, I have shown you how to combine two Map in Java using the putAll() method but there was a problem. If a key is present in both maps, then putAll() overrides the value from the second map, which you may or may not want. 

The real issue is that you don't have any control on what should happen if there exists duplicate keys in those Map e.g. you may want to keep the original value intact or you always want values from second map to override values in first map, or you just want to concatenate or add two values to form a new value.

This problem is solved by the Map.merge() method in JDK 8, which allows you to pass a lambda expression to control the remapping operation.


What does Map.merge() method work?

The merge() method is a default method added on java.util.Map interface on JDK 8. It accepts three parameters key, value and a BiFunction which is a functional interface (if you don't know what is functional interface, check this article).

If the given key is not already associated with a value in the map or is associated with null then it associates that with the given non-null value. 

Otherwise i.e. when the key is already present in the map then it replaces the associated value with the results of the given remapping function. The remapping function is the third parameter which is a BiFunction

If you are new to functional programming, A BiFunction<T, U, R> accepts two arguments T and U and return a result of type R. In case of merge, the BiFunction argument must be the type of values as seen here:

BiFunction<? super V,? super V,? extends V> remappingFunction)


Because it is a functional interface, you can pass a lambda expression to it and can do a lot of things. For example, you can retain values from the original map or you can add them if they are numbers or concatenate them if they are String

You can even use merge() method to remove a mapping. For example, if the remapping function returns null then the mapping is removed. Similarly, if If the re-mppaing function itself throws an (unchecked) exception, the exception is rethrown, and the current mapping is left unchanged.

How to merge two Map in Java 8 - Map.merge() example



How to merge two HashMap in Java?

Suppose you have two maps, first and second and you want to merge them, for that you can either to first.merge(second) or second.merge(first), both will give you the same result but in case of former, second map will remain same and in later example first map will remain unchanged.

Let's assume we want to do first.merge(second) but since merge need key, value and a mapping function, we need to iterate through the second map and call the merge() method for each key and value as shown below:

for(Map.Entry<String, String> entry: secondMap.entrySet()){
    firstMap.merge(entry.getKey(),entry.getValue(),(v1, v2) -> v1);
}

In this case we are keeping the value from first map in case of duplicate key. If you want to use the value from the second map for duplicate keys then you can just use v2 in the lambda expression passed as remapping function, as shown below:

for(Map.Entry<String, String> entry: secondMap.entrySet()){

  firstMap.merge(entry.getKey(), entry.getValue(), (v1, v2) -> v1);
}


If you want to combine them using ":" then you can also do like this:

for(Map.Entry<String, String> entry: secondMap.entrySet()){

firstMap.merge(entry.getKey(), 
entry.getValue(),
(v1, v2) -> (v1 + ":" + v2);

}

In short, you have access to both values from first and second map and you can do whatever you want to do. Just don't return null because it will then remove the mapping from the merged map. 

Btw, you can also replace the enhanced for loop with the forEach() method of Java 8, which is also available to Map interface as below:


secondMap.forEach((key, value) -> 
firstMap.merge(key, value, (v1, v2) -> (v1 + " " + v2)));

This is much more clear and succinct and simpler. Here we are iterating over second map and passing each key and value to the merge() function called on first map. The forEach() function takes a BiConsumer i.e. a function which takes two values and return nothing. The call to firstMap.merge() is donning exactly that. 

By the way, you can also throw RuntimeException or AsseritionError in case of duplicate keys as shown below:


secondMap.forEach((key, value) -> 

firstMap.merge(key, value,

(v1, v2) -> {

throw new AssertionError("duplicate values for key: "+ key); })); 


This will throw following error when you run this code:

Exception in thread "main" java.lang.AssertionError: 
duplicate values for key: joshbloch
at Helloworld.lambda$1(Helloworld.java:55)
at java.util.HashMap.merge(HashMap.java:1253)
at Helloworld.lambda$0(Helloworld.java:55)
at java.util.HashMap.forEach(HashMap.java:1288)
at Helloworld.main(Helloworld.java:55)


because the key "joshbloch" is present in both the map. Let's see a couple of examples of merge() method in Java 8 to understand it better. 




Java Program to merge two HashMaps using Map.merge() in JDK 8

Here is the complete Java program to demonstrate how to use the merge function in Java 8 for combining values from two map. In this case I have two maps, where I have stored twitter handles of popular Java personalities and their first and name e.g. joshbloch, which is Twitter handler of Joshua Bloch, author of Effective Java and Java Puzzlers and BrianGoetz is twitter handle for Brian Goetz, author of another popular Java book, Java Concurrency in Practice.

The first map contains twitter handle to first name mapping and second map contains Twitter handle to last name mapping. If both map contains same handle e.g. joshbloch and springrod is present in both map then we have a clash and that's where power of merge() function is show cased. 

In case of first example, we have used value from the first map in case of key clash, while in the second example we have used the value from the second map. 

On the third example, we have combined values from both map to create full name e.g. joshbloch mapped to Joshua Bloch in final map

import java.util.HashMap;

import java.util.Map;



public class Helloworld {



public static void main(String[] args) {



// first map author to book

Map<String, String> firstMap = new HashMap<String, String>();

firstMap.put("joshbloch", "Joshua");

firstMap.put("cutting", "Doug");

firstMap.put("BrianGoetz", "Brian");

firstMap.put("springrod", "Rod");





System.out.println("first map: " + firstMap);



// second map - author to book

Map<String, String> secondMap = new HashMap<>();

secondMap.put("joshbloch", "Bloch");

secondMap.put("mjpt777", "Thompson");

secondMap.put("springrod", "Johnson");

secondMap.put("odersky", "Odersky");

secondMap.put("seriouspony", "Sierra");



System.out.println("second map: " + secondMap);



// when you merge map, it would contains mapping

// from the two maps, but for duplicate keys

// you have choice. You can specify what do you want

// to do with values e.g. overwriting or just concatenating them

// you can choose any map to source and destination

// for example, in below code, autorToBook map

// will contain combined value but authorToBook2 will

// not be changed.



for(Map.Entry<String, String> entry: secondMap.entrySet()){

firstMap.merge(entry.getKey(), entry.getValue(), (v1, v2) -> v1);

}





System.out.println("merged with values retained from first 
map in case of duplicate keys: " 
+ firstMap);



// you can either use for loop or forEach method to merge two maps

// here we are keeping value from second map in case of clash of keys

secondMap.forEach((key, value) -> firstMap.merge(key, value, 
(v1, v2) -> v2)); 

System.out.println("merged with values retained from second map 
in case of duplicate keys: " 
+ firstMap);



// combining value with a space from both the map if key is same

secondMap.forEach((key, value) -> firstMap.merge(key, value, (v1, v2) 
-> (v1 + " " + v2))); 

System.out.println("merged with values retained from second map in 
case of duplicate keys: "
 + firstMap);



// btw, you can also throw unchecked exception or error in case of 
// key clash

secondMap.forEach((key, value) -> firstMap.merge(key, value, (v1, v2) -> 
{throw new AssertionError("duplicate values for key: "+ key); })); 

}



}





Output:

first map: {joshbloch=Joshua, cutting=Doug, springrod=Rod, BrianGoetz=Brian}

second map: {seriouspony=Sierra, odersky=Odersky, joshbloch=Bloch, 
springrod=Johnson, mjpt777=Thompson}

merged with values retained from first map in case of duplicate keys: 
{seriouspony=Sierra, odersky=Odersky, joshbloch=Joshua, cutting=Doug, 
springrod=Rod, mjpt777=Thompson, BrianGoetz=Brian}

merged with values retained from second map in case of duplicate keys: 
{seriouspony=Sierra, odersky=Odersky, joshbloch=Bloch, cutting=Doug, 
springrod=Johnson, mjpt777=Thompson, BrianGoetz=Brian}

merged with values retained from second map in case of duplicate keys: 
{seriouspony=Sierra, odersky=Odersky, joshbloch=Joshua Bloch, 
cutting=Doug, springrod=Rod Johnson, mjpt777=Thompson, BrianGoetz=Brian}



Exception in thread "main" java.lang.AssertionError: 
duplicate values for key: joshbloch

at Helloworld.lambda$1(Helloworld.java:55)

at java.util.HashMap.merge(HashMap.java:1253)

at Helloworld.lambda$0(Helloworld.java:55)

at java.util.HashMap.forEach(HashMap.java:1288)

at Helloworld.main(Helloworld.java:55)


You can see in case of first example joshbloch=Bloch i.e. value from first map is used, while in case of second example joshbloch=Bloch, means value from second map is used, and in case of third example, joshbloch=Joshua Bloch, value from both map is combined. You can also see that the merged map contains entries from both the maps.

As I said, if you don't want to handle duplicate keys, you can also throw AssertionError to indicate that, though you should remember to print the key for troubleshooting purpose. 

That's all about how to merge two Map in Java. As I said, prior to JDK 7, you can use the putAll() method to merge or combine two map but you don't have control over values if map contains same keys. This problem is solved using the merge() function in Java 8, which allows you to pass a lambda expression to compute new value. 

You can either keep the value from first map, second map, or create a new value by combining values from both the map. The mapping can also be removed if your lambda expression evaluate to null.

Other Java Map tutorial you may like to read:


Thanks for reading this article so far. If you like this example of merging two map in Java then please share with your friends and colleagues. If you have any question or feedback then please drop a comment.

No comments:

Post a Comment