Monday, October 18, 2021

10 Examples of Converting a List to Map in Java 8

Hello guys, 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 and values. Since it's an 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 a 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 full of non-trivial examples and once you went through that you won't need to lookup Google for your day-to-day task in Java 8.

And, If you are serious about improving Java functional programming skills then I highly recommend you check out the  Learn Java Functional Programming with Lambdas & Streams by Rang Rao Karnam on Udemy, which explains both Functional Programming and Java Stream fundamentals in good detail



How to convert a List to Map in Java

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



1. 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 each element into a HashMap, where ISBN code is the key, and the 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.


2. 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 the above code example, the stream() method returns 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.





3. Using Java 8 method reference

You can further simplify 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 book 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 the 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);

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

You can see that the List contains 4 elements but our Map contains only three mappings because one of the elements "Visa" is duplicate. The Collector only kept the first reference of "Visa" and discarded the second one. Alternatively, you can also remove duplicates from the 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 lose the ordering of elements 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 the 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 the 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()
                .collect(Collectors.toMap(Function.identity(),
                                String::length,
                                (e1, e2) -> e1,
                                LinkedHashMap::new));
        System.out.println("map: " + cards2Length);

    }

}

Output:
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 is exactly the same. So use this version of the Collectors.toMap() method if you want to preserve the 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 is just a  class that implements the Map interface. It will not preserve the order of elements if you want to keep the order the same as in the 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.


Related Java 8 Tutorials
If you are interested in learning more about the new features of Java 8, here are my earlier articles covering some of the important concepts of Java 8:
  • 20 Examples of Date and Time in Java 8 (tutorial)
  • How to use Stream class in Java 8 (tutorial)
  • How to use filter() method in Java 8 (tutorial)
  • How to use forEach() method in Java 8 (example)
  • How to join String in Java 8 (example)
  • How to convert List to Map in Java 8 (solution)
  • How to use peek() method in Java 8 (example)
  • 5 Books to Learn Java 8 from Scratch (books)
  • How to convert the stream to array in Java 8 (tutorial)
  • Java 8 Certification FAQ (guide)
  • Java 8 Mock Exams and Practice Test (test)

Thanks for reading this article so far. If you like this article then please share it with your friends and colleagues. If you have any questions, doubts, or feedback then please drop a comment and I'll try to answer your question.

8 comments:

  1. Very useful tip, thank you very much.

    ReplyDelete
  2. Hi Javin,
    I want to convert List> to List using java8 lambda,
    All the key and in map and employee class fields are same.

    ReplyDelete
  3. Hello Javin.
    You have misspelled "Function.identi[f]y()" in the article.

    ReplyDelete
  4. Thanks Tomasz, looks like my spell checker messed it. Please read it as Function.identity(), there is no such method like identify().

    ReplyDelete
  5. Map> result// how to write stream for this kind of variable in stream

    ReplyDelete
  6. What if for Map , how can write it in method reference as mentioned in 3 step?

    i need something like this

    Collectors.toMap(JsonObject::getString("key"), jobj -> jobj)

    ReplyDelete
  7. Helps a lot because of detailed explanation thank you javarevisted team

    ReplyDelete