One of the common tasks in Java is to convert a List of objects, like a List<T> into a Map, I mean Map<K, V>, where K is some property of the object and V is the actual object. For example, suppose you have a List<Order>, and you want to convert it into a Map, e.g. Map<OrderId, Order>, how do you that? Well, the simplest way to achieve this is iterating over List and add each element to the Map by extracting keys and using the actual element as an object. This is exactly what many of us do in the pre-Java 8 world, but JDK 8 has made it even simpler.
In Java 8, you can get the stream from List and then collect all elements into a Map by using a Collector. The collect() method of Stream class and java.util.stream.Collectors class gives you ample choices to decide which property goes into the key and which object goes into the value.
Also, In most cases, you convert an ArrayList to HashMap or LinkedHashMap, depending upon the scenario, so the problem of converting a List to Map is actually the same as the problem of converting an ArrayList to HashMap or LinkedHashMap because ArrayList is a List and HashMap is a Map. I'll show you an example of this shortly.
Btw, in general, when you convert a List to a Map, you have to keep in mind some of the nuisances which come from the fact that they are two different data structures with different properties.
For example, a List is an ordered collection that allows duplicate elements, but Map doesn't provide any ordering guarantee, and it doesn't allow duplicate keys (see Java Fundamentals: Collections). This means you may lose the original order of objects in the List if you are not careful.
Similarly, it may be possible that the List you are converting into a Map may contain some duplicates, which may not be a problem in the traditional way because when you insert an existing key into the Map, it overwrites the old value, which would be the same object in case of duplicate.
But, it does pose a problem if you try to collect duplicate elements from Stream into a Map, without telling Collector how to resolve the tie (see From Collections to Streams in Java 8 Using Lambda Expressions, a popular online Java 8 course from Pluralsight).
In short, you may get "Exception in thread "main" java.lang.IllegalStateException: Duplicate key" exception while converting an ArrayList with duplicate elements into a HashMap in Java 8.
You can solve this problem by telling the Collector interface about how to handle duplicates. The toMap() method, which we'll use to convert an ArrayList to HashMap, is overloaded, and it allows you to specify which elements to keep and which element to discard in case of duplicates.
Enough of theory, now, let's begin coding now.
This code also handles any duplicate in the list well because it is using the put() method to insert entries that override values in case of duplicate keys, but no error or exception is thrown.
Map<String, Integer> map = new HashMap<>();
for(String str: listOfString){
map.put(str, str.length());
}
In this code, I have chosen a HashMap, but you are free to select any kind of map, e.g. LinkedHashMap or TreeMap depending upon your requirement.
You can even use a ConcurrentHashMap, if you want to, Btw, You should use a LinkedHashMap if you're going to preserve order though.
Map<String, Integer> map8 = listOfString.stream().collect(toMap(s -> s , s -> s.length()));
The first argument of toMap is a crucial mapper, and the second is a value mapper. We are using lambda expression which means pass element itself as a key (s -> s), and it's the length as value (s -> s.length), here, s represents the current element of Stream, which is String; hence we are able to call the length() method.
The Lambda is very good at type inference, you can see What's New in Java 8, another free course from Pluralsight to learn more about lambda expression in Java.
You can see here we are passing Function.identity() instead of giving the value itself, but, we are using HashMap, which means the order will not be guaranteed, See the difference between HashMap and LinkedHashMap for more details.
Converting ArrayList to LinkedHashMap in Java 8
In this case, we are using LinkedHashMap instead of HashMap, which means the order of elements will be the same as in List because of LinkedHashMap preserver the insertion order. See The Complete Java MasterClass, one of the comprehensive Java courses from Udemy.
So, we have a list of String, and we'll generate a map of String keys and their length as value, sounds exciting right, well it is.
We'll progressively move from traditional, iterative Java solution to advanced, functional Java 8 solution, starting with the lambda expressions and moving to method reference and dealing with more practical scenarios like converting list with duplicate objects and keeping the order of elements intact in the generated map.
From the output, you can see that the first generated map has lost the order; in the list, Ruby comes last, but on the map, Python came last.
The same is true for the second example because we are not specifying which type of Map we want to Collectors; hence, it is returning a Map implementation that doesn't provide any ordering guarantee (see Streams, Collectors, and Optional for Data Processing in Java 8).
But, if you look at the last example, the order of entries in the Map is the same as they were in the original List because we have asked the Collectors and toMap() method to collect elements in a LinkedHashMap, which keeps entries in the order they are inserted.
Regarding duplicates, you can see that our list contains duplicate elements, Java came twice before the 4th example, but Map doesn't contain copy, and it didn't throw any exception or error either because we have provided a merge function to the toMap() method to choose between duplicate values.
2) Use the static import feature to import static methods of Collectors, e.g. toMap(); this will simplify your code.
3) The toMap(keyExtractor, valueExtractor) doesn't provide any guarantee of what kind of map it will return.
4) If your List contains duplicate elements and you are using them as the key, then you should use toMap(keyMapper, valueMapper, mergeFunction). The merge function is used to resolve collisions between values associated with the same key, as supplied to Map.merge(Object, Object, BiFunction). See Java SE 8 for Really Impatient to learn more about the merge() function of the Map interface in Java 8.
5) If you want to maintain the order of entries in the Map same as in the original list, then you should use the toMap(keyMapper, valueMapper, mergeFunction, mapSupplier) method, where mapSupplier is a function which returns a new, empty Map into which the results will be inserted. You can supply LinkedHashMap:: new using constructor reference to collect results in a LinkedHashMap, which guarantees the insertion order.
6) Replace lambda expression with method reference for brevity and simplified code.
That's all about how to convert a List to Map in Java 8, particularly an ArrayList to HashMap and LinkedHashMap. As I said, it's pretty easy to do that using stream and collector.
The Collectors, which is a static utility class similar to Collections, provide several options to collect elements of a stream into the different types of collection and the toMap() method can be used to collect elements into a Map.
Though this method is overloaded and by default doesn't guarantee which type of Map it will return like HashMap, TreeMap, or LinkedHashMap, you need to tell him about that.
Similarly, you also have to be mindful of ordering and duplicate elements. If you want the order of elements should be the same as they are in the original list, then you should use LinkedHashMap as an accumulator to collect mappings. Similarly, use the toMap() version, which allows you to deal with duplicate keys.
Other Java 8 articles and tutorials you may like to explore
Thanks for reading this article so far. If you really like this tutorial and my tips, then please share them with your friends and colleagues. If you have any questions or feedback, then please drop me a note.
P.S.- If you just want to learn more about new features in Java 8, then you can also see this list of Free Java 8 Courses on FreeCodeCamp. It explains all the essential features of Java 8 like lambda expressions, streams, functional interfaces, Optional, new Date Time API, and other miscellaneous changes.
In Java 8, you can get the stream from List and then collect all elements into a Map by using a Collector. The collect() method of Stream class and java.util.stream.Collectors class gives you ample choices to decide which property goes into the key and which object goes into the value.
Also, In most cases, you convert an ArrayList to HashMap or LinkedHashMap, depending upon the scenario, so the problem of converting a List to Map is actually the same as the problem of converting an ArrayList to HashMap or LinkedHashMap because ArrayList is a List and HashMap is a Map. I'll show you an example of this shortly.
Btw, in general, when you convert a List to a Map, you have to keep in mind some of the nuisances which come from the fact that they are two different data structures with different properties.
For example, a List is an ordered collection that allows duplicate elements, but Map doesn't provide any ordering guarantee, and it doesn't allow duplicate keys (see Java Fundamentals: Collections). This means you may lose the original order of objects in the List if you are not careful.
How about Order of Elements?
Though, if you care for the order you can opt for a Map implementation that provides some sort of ordering guarantee, like LinkedHashMap which guarantee insertion order (the order in which mappings are added into the map), and TreeMap which is a sorted map, and sort objects in their natural order of any order imposed by provided Comparator. See the difference between HashMap, TreeMap, and LinkedHasMap for more details.Similarly, it may be possible that the List you are converting into a Map may contain some duplicates, which may not be a problem in the traditional way because when you insert an existing key into the Map, it overwrites the old value, which would be the same object in case of duplicate.
But, it does pose a problem if you try to collect duplicate elements from Stream into a Map, without telling Collector how to resolve the tie (see From Collections to Streams in Java 8 Using Lambda Expressions, a popular online Java 8 course from Pluralsight).
In short, you may get "Exception in thread "main" java.lang.IllegalStateException: Duplicate key" exception while converting an ArrayList with duplicate elements into a HashMap in Java 8.
You can solve this problem by telling the Collector interface about how to handle duplicates. The toMap() method, which we'll use to convert an ArrayList to HashMap, is overloaded, and it allows you to specify which elements to keep and which element to discard in case of duplicates.
Enough of theory, now, let's begin coding now.
How to convert ArrayList to HashMap before Java 8
This is the ideal way to convert a List to Map in Java. We are iterating over List using enhanced for loop and inserting String as a key into a HashMap and its length as a value into HashMap.This code also handles any duplicate in the list well because it is using the put() method to insert entries that override values in case of duplicate keys, but no error or exception is thrown.
Map<String, Integer> map = new HashMap<>();
for(String str: listOfString){
map.put(str, str.length());
}
In this code, I have chosen a HashMap, but you are free to select any kind of map, e.g. LinkedHashMap or TreeMap depending upon your requirement.
You can even use a ConcurrentHashMap, if you want to, Btw, You should use a LinkedHashMap if you're going to preserve order though.
Converting ArrayList to HashMap in Java 8 using a Lambda Expression
This is the modern way of turning a list into a map in Java 8. First, it gets the stream from the list, and then it calls the collect() method to collect all elements using a Collector. We are passing a toMap() method to tell Collector that use Map to collect elements.Map<String, Integer> map8 = listOfString.stream().collect(toMap(s -> s , s -> s.length()));
The first argument of toMap is a crucial mapper, and the second is a value mapper. We are using lambda expression which means pass element itself as a key (s -> s), and it's the length as value (s -> s.length), here, s represents the current element of Stream, which is String; hence we are able to call the length() method.
The Lambda is very good at type inference, you can see What's New in Java 8, another free course from Pluralsight to learn more about lambda expression in Java.
Converting ArrayList to HashMap using method reference in Java 8
Whenever you use a lambda expression, pause, and think if you can replace lambda with a method reference because it makes your code cleaner. Lambda is nothing but code, and if you already have a method that does the same thing, then you can pass the method reference instead of a lambda expression, as shown here.ashMap<String, Integer> hash
= listOfString
.stream()
.collect(toMap(Function.identity(), String::length, (e1, e2) -> e2, HashMap::new));
You can see here we are passing Function.identity() instead of giving the value itself, but, we are using HashMap, which means the order will not be guaranteed, See the difference between HashMap and LinkedHashMap for more details.
Converting ArrayList to LinkedHashMap in Java 8
LinkedHashMap<String, Integer> linked = listOfString.stream() .collect(toMap(Function.identity(), String::length, (e1, e2) -> e2, LinkedHashMap::new)); System.out.println("generated linkedhashmap:" + linked);
In this case, we are using LinkedHashMap instead of HashMap, which means the order of elements will be the same as in List because of LinkedHashMap preserver the insertion order. See The Complete Java MasterClass, one of the comprehensive Java courses from Udemy.
Java Program to convert List to Map in JDK 8
Earlier I wanted to use a user or domain object like Order or Book to demonstrate this example, but I decided against it in favor of String to keep the program simple. Since almost every Java developer knows about String, it makes the application much more acceptable, and the focus remains only on Java 8 features.So, we have a list of String, and we'll generate a map of String keys and their length as value, sounds exciting right, well it is.
We'll progressively move from traditional, iterative Java solution to advanced, functional Java 8 solution, starting with the lambda expressions and moving to method reference and dealing with more practical scenarios like converting list with duplicate objects and keeping the order of elements intact in the generated map.
import static java.util.stream.Collectors.toMap; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Function; /* * Java Program to convert a List to Map in Java 8. * We'll convert an ArrayList of String to an HashMap * where key is String and value is their length */ public class Demo { public static void main(String[] args) throws Exception { // an ArrayList of String object List<String> listOfString = new ArrayList<>(); listOfString.add("Java"); listOfString.add("JavaScript"); listOfString.add("Python"); listOfString.add("C++"); listOfString.add("Ruby"); System.out.println("list of string: " + listOfString); // converting ArrayList to HashMap before Java 8 Map<String, Integer> map = new HashMap<>(); for (String str : listOfString) { map.put(str, str.length()); } System.out.println("generated map: " + map); // converting List to Map in Java 8 using lambda expression Map<String, Integer> map8 = listOfString.stream().collect( toMap(s -> s, s -> s.length())); System.out.println("generated map: " + map); // using method reference map8 = listOfString.stream().collect( toMap(Function.identity(), String::length)); // convert list with duplicate keys to HashMap listOfString.add("Java"); System.out.println("list of string with duplicates: " + listOfString); HashMap<String, Integer> hash = listOfString.stream() .collect( toMap(Function.identity(), String::length, (e1, e2) -> e2, HashMap::new)); System.out.println("generated hashmap:" + hash); // keep the order same as original list while conversion LinkedHashMap<String, Integer> linked = listOfString.stream().collect( toMap(Function.identity(), String::length, (e1, e2) -> e2, LinkedHashMap::new)); System.out.println("generated linkedhashmap:" + linked); } } Output: list of string: [Java, JavaScript, Python, C++, Ruby] generated map: {Java=4, C++=3, JavaScript=10, Ruby=4, Python=6} generated map: {Java=4, C++=3, JavaScript=10, Ruby=4, Python=6} list of string with duplicates: [Java, JavaScript, Python, C++, Ruby, Java] generated hashmap:{Java=4, C++=3, JavaScript=10, Ruby=4, Python=6} generated linkedhashmap:{Java=4, JavaScript=10, Python=6, C++=3, Ruby=4}
From the output, you can see that the first generated map has lost the order; in the list, Ruby comes last, but on the map, Python came last.
The same is true for the second example because we are not specifying which type of Map we want to Collectors; hence, it is returning a Map implementation that doesn't provide any ordering guarantee (see Streams, Collectors, and Optional for Data Processing in Java 8).
But, if you look at the last example, the order of entries in the Map is the same as they were in the original List because we have asked the Collectors and toMap() method to collect elements in a LinkedHashMap, which keeps entries in the order they are inserted.
Regarding duplicates, you can see that our list contains duplicate elements, Java came twice before the 4th example, but Map doesn't contain copy, and it didn't throw any exception or error either because we have provided a merge function to the toMap() method to choose between duplicate values.
Important points:
1) You can use the Function.identity() function if you are passing the object itself in the lambda expression. For example, lambda expression s -> s can be replaced with Function.identity() call.2) Use the static import feature to import static methods of Collectors, e.g. toMap(); this will simplify your code.
3) The toMap(keyExtractor, valueExtractor) doesn't provide any guarantee of what kind of map it will return.
4) If your List contains duplicate elements and you are using them as the key, then you should use toMap(keyMapper, valueMapper, mergeFunction). The merge function is used to resolve collisions between values associated with the same key, as supplied to Map.merge(Object, Object, BiFunction). See Java SE 8 for Really Impatient to learn more about the merge() function of the Map interface in Java 8.
5) If you want to maintain the order of entries in the Map same as in the original list, then you should use the toMap(keyMapper, valueMapper, mergeFunction, mapSupplier) method, where mapSupplier is a function which returns a new, empty Map into which the results will be inserted. You can supply LinkedHashMap:: new using constructor reference to collect results in a LinkedHashMap, which guarantees the insertion order.
6) Replace lambda expression with method reference for brevity and simplified code.
That's all about how to convert a List to Map in Java 8, particularly an ArrayList to HashMap and LinkedHashMap. As I said, it's pretty easy to do that using stream and collector.
The Collectors, which is a static utility class similar to Collections, provide several options to collect elements of a stream into the different types of collection and the toMap() method can be used to collect elements into a Map.
Though this method is overloaded and by default doesn't guarantee which type of Map it will return like HashMap, TreeMap, or LinkedHashMap, you need to tell him about that.
Similarly, you also have to be mindful of ordering and duplicate elements. If you want the order of elements should be the same as they are in the original list, then you should use LinkedHashMap as an accumulator to collect mappings. Similarly, use the toMap() version, which allows you to deal with duplicate keys.
Other Java 8 articles and tutorials you may like to explore
- The Complete Java Developer RoadMap
- Java 8 filter + map + collect example
- How to join String in Java 8
- 5 Free Online Courses to Learn Java 8 and 9
- How to sort the map by keys in Java 8?
- How to sort HasMap by values in Java 8?
- 10 examples of Optional in Java 8?
- How to use the forEach method in Java 8
- How to use the filter method in Java 8
- 10 Examples of Stream in Java 8
- How to sort a Map by values in Java 8
- 5 Course to Master Java 8 Programming
Thanks for reading this article so far. If you really like this tutorial and my tips, then please share them with your friends and colleagues. If you have any questions or feedback, then please drop me a note.
P.S.- If you just want to learn more about new features in Java 8, then you can also see this list of Free Java 8 Courses on FreeCodeCamp. It explains all the essential features of Java 8 like lambda expressions, streams, functional interfaces, Optional, new Date Time API, and other miscellaneous changes.
Hi.. how to convert to map without using Java 8 if the list contains duplicate
ReplyDeleteHello there, just loop through the list and add elements into map, if list contains duplicate then also no effect because it will override the earlier one, but no error and end result will be same.
DeleteI don't see how the code readability improved from one approach to the other. It seems like just as complicated as the traditional approach.
ReplyDeleteHere toMap is user defind method in collector, please help me, to define toMap method please sir..
ReplyDeleteNo, it's an API method, you don't need to create anything, just use it.
Delete