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 HashMap in Java?
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
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:
- How to filter values from a map in Java?
- 40 HashMap Interview Questions with Answers
- How to sort a map by keys in Java 8?
- 10 ConcurrentHashMap Interview Questions Answers
- How to remove a mapping from a map in Java 8?
- How to sort a map by values in Java 8?
- 10 Example of ConcurrentHashMap in Java
- How to check if a key exists in the map?
- How to iterate over a map in Java?
- 21 HashMap Interview Questions with Answers
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