10 Examples of Converting a List to Map in Java 8

Suppose you have a List of objects, List and you want to convert that to a Map, where a key is obtained from the object and value is the object itself, how do you do it by using Java 8 stream and lambda expression? Prior to Java 8, you can do this by iterating through the List and populating the map by keys the and values. Since it's iterative approach and if you are looking for a functional solution then you need to use the stream and lambda expression, along with some utility classes like Collectors, which provides several useful methods to convert Stream to List, Set or Map. In the past, we have seen how to use the Collectors.groupingBy() method to group elements in Set and In this article, we will use Collectors.toMap() method to convert a List of an object into a Map in Java.

Remember, the Map returned by Collector is not necessarily HashMap or LinkedHashMap, if you want to use any special Map type, you need to tell the Collector about it as shown in the second example.

In the similar note, if you have just started learning Java 8 and come here to solve a problem you are facing in your day to day life while converting a Java SE 6 or 7 code to Java 8, then I suggest going through a book like Java SE 8 for Really Impatient. It's one of the better books with full of non-trivial example and once you went through that you won't need to look up Google for your day to day task in Java 8.

Java 8 Example to convert a List to Map

How to convert a List to Map in Java

Now, let's see different ways to solve this problem in the pre-JDK 8 world and in Java 8. This comparative analysis will help you to learn the concept and Java 8 API better.

Before Java 8
Here is how you can convert a List to Map in Java 5, 6 or 7:

private Map<String, Choice> toMap(List books) {
        final Map hashMap = new HashMap<>();
        for (final Book book : books) {
            hashMap.put(book.getISBN(), book);
        return hashMap;

You can see we have iterated through the List using enhanced for loop of Java 5 and put the each element into a HashMap, where ISBN code is the key and book object itself is the value. This is the best way to convert a List to Map in pre-JDK 8 worlds. It's clear, concise and self-explanatory, but iterative.

Java 8 using Lambdas
Now, let's see how we can do the same in Java 8 by using lambda expression and Stream API, here is my first attempt:

Map<String, Book> result  = books.stream()
            .collect(Collectors.toMap(book -> book.getISBN, book -> book));

In above code example, the stream() method return a stream of Book object from the List and then I have used collect() method of Stream class to collect all elements. All the magic of how to collect elements happening in this method.

I have passed the method Collectors.toMap(), which means elements will be collected in a Map, where the key will be ISBN code and value will be the object itself. We have used a lambda expression to simplify the code.

Using Java 8 method reference
You can further simply the code in Java 8 by using method reference, as shown below:

Map<String, Book> result =  books.stream()
        .collect(Collectors.toMap(Book::getISBN, b -> b));

Here we have called the getISBN() method using method reference instead of using a lambda expression.

You can further remove the last remaining lambda expression from this code, where we are passing the object itself by using Function.identify() method in Java 8 when the value of the Map is the object itself, as shown below:

Map<String, Book> result = choices.stream()
        .collect(Collectors.toMap(Book::getISBN, Function.identity()))

What does identify function do here? It's just a substitute of b ->b and you can use if you want to pass the object itself. See Java SE 8 for Really Impatient to learn more about Function.identity() method.

10 Examples of converting List to Map with duplicates

How to convert a List with Duplicates into Map in JDK 8

What if List has duplicates? When you are converting List to Map, you must pay attention to a different characteristic of these two collection classes, a List allows duplicate elements, but Map doesn't allow duplicate keys. What will happen if you try to convert a List with duplicate elements into a Map in Java 8?

Well, the above method will throw IllegalStateException as shown in the following example:

List cards = Arrays.asList("Visa", "MasterCard", "American Express", "Visa");
Map cards2Length = cards.stream()
                .collect(Collectors.toMap(Function.identity(), String::length));

Exception in thread "main" java.lang.IllegalStateException: Duplicate key 4
at java.util.stream.Collectors.lambda$throwingMerger$90(Collectors.java:133)
at java.util.stream.Collectors$$Lambda$3/1555009629.apply(Unknown Source)
at java.util.HashMap.merge(HashMap.java:1245)
at java.util.stream.Collectors.lambda$toMap$148(Collectors.java:1320)
at java.util.stream.Collectors$$Lambda$5/258952499.accept(Unknown Source)
at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at Java8Demo.main(Java8Demo.java:20)

This exception is suggesting that 4th element of the List is a duplicate key. Now how do you solve this problem? Well, Java 8 has provided another overloaded version of Collectors.toMap() function which accepts a merge function to decide what to do in case of the duplicate key. If you use that version, instead of throwing an exception, Collector will use that merge function to resolve a conflict.

In the following example, I have used that version and instructed to use the first object in case of the duplicate key, the lambda expression (e1, e2) -> e1 is suggesting that.

You can do whatever you want e.g. you can combine the keys or choose any one of them.

List cards = Arrays.asList("Visa", "MasterCard", "American Express", "Visa");
System.out.println("list: " + cards);
Map cards2Length = cards.stream()
                .collect(Collectors.toMap(Function.identity(), String::length, (e1, e2) -> e1));
System.out.println("map: " + cards2Length);

list: [Visa, MasterCard, American Express, Visa
 map: {American Express=16, Visa=4, MasterCard=10}

You can see that List contains 4 elements but our Map contains only three mappings because one of the element "Visa" is duplicate. The Collector only kept the first reference  of "Visa" and discarded the second one. Alternatively, you can also remove duplicates from List before converting it to Map as shown here.

How to convert a List to map and keep order

How to Preserve Order of Elements when converting a List to Map

Remember I said that Map returned by the Collectors.toMap() is a just a simple implementation of Map interface and because Map doesn't guarantee the order of mappings, you will likely to lose the ordering of element provided by the List interface.

If you really need elements in Map in the same order they were in the List, you can use another version of Collectors.toMap() method which accepts four parameters and the last one of them is to ask for a specific Map implementation e.g. HashMap or LinkedHaashMap.

Since LinkedHashMap maintains insertion order of elements (see here), you can collection elements in the LinkedHashMap as shown in the following example:

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

 * Java Program to convert a List to map in Java 8.
 * This example shows a trick to preserve order of element
 * in the list while converting to Map using LinkedHashMap. 
public class Java8Demo {

    public static void main(String args[]) {

        List<String> hostingProviders = Arrays.asList("Bluehost", "GoDaddy", "Amazon AWS", "LiquidWeb", "FatCow");
        System.out.println("list: " + hostingProviders);

        Map<String, Integer> cards2Length = hostingProviders.stream()
                                (e1, e2) -> e1,
        System.out.println("map: " + cards2Length);



list: [Bluehost, GoDaddy, Amazon AWS, LiquidWeb, FatCow]
map: {Bluehost=8, GoDaddy=7, Amazon AWS=10, LiquidWeb=9, FatCow=6}

You can see that order of elements in both List and Map are exactly same. So use this version of Collectors.toMap() method if you want to preserve ordering of elements in the Map.

That's all about how to convert a List to Map in Java 8 using lambda expression and Streams. You can see it's much easier and concise using the lambda expression. Just remember that the Map returned by the Collectors.toMap() is not your regular HashMap, it just a  class which implements Map interface. It will not preserve the order of elements if you want to keep the order same as in original list then use the LinkedHashMap as shown in the last example.

Also, don't forget to provide a merge function if you are not sure about whether your List will contain duplicates or not. It will prevent the IllegalStateException you get when your List contains duplicates and you want to convert it to a Map, which doesn't allow duplicate keys.


Anonymous said...

Very useful tip, thank you very much.

Felipe Bachman said...

nice post

Post a Comment