Java 8 - Convert ArrayList to HashMap or LinkedHashMap - Example Tutorial

One of the common task in Java is to convert a List of object e.g. List<T> into a Map e.g. 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 many of us do it in 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 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 structure with different properties.

For example, a List is an ordered collection which 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.

Though, if you care for order you can opt for a Map implementation which provides some sort of ordering guarantee e.g. 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 free online Java 8 course from Plurasight).

In short, you may get "Exception in thread "main" java.lang.IllegalStateException: Duplicate key" exception while converting an ArrayList with duplicate elements into an HashMap in Java 8.

You can solve this problem by telling 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 ...

How to convert ArrayList to HashMap before Java 8

This is the classic 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 an 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 which 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 an HashMap but you are free to choose 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 want to preserve order though.



Converting ArrayList to HashMap in Java 8 using lambda expression

This is the modern way of converting a list to map in Java 8. First, it gets the stream from the list and then it calls the collect() method to collect all element using a Collector. We are passing a toMap() method to tell Collector that use Map to collect element.

Map<String, Integer> map8 = listOfString.stream().collect(toMap(s -> s , s -> s.length()));

The first argument of toMap is a key mapper and second is a value mapper. We are using lambda expression which means pass element itself as a key (s -> s) and it's 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.

Convert ArrayList to HashMap or LinkedHashMap - Java 8



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 which does the same thing then you can pass the method reference instead of a lambda expression, as shown here.

HashMap<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 passing 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 same as in List because of LinkedHashMap preserver the insertion order. See Java Fundamentals: Collections, a free Java course from Pluralsight.



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 program much more acceptable and 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 interesting 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 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 list Ruby comes last but in the map, Python came last.

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 which doesn't provide any ordering guarantee (see Streams, Collectors, and Optional for Data Processing in Java 8).

Java 8 - Convert ArrayList to HashMap or LinkedHashMap


But, if you look at the last example, the order of entries in the Map is same as they were in original List because we have asked 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 4th example but Map doesn't contain duplicate and it didn't throw any exception or error either because we have provided a merge function to 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 important 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 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 merge() function of Map interface in Java 8.

Java 8 - Convert ArrayList to HashMap or LinkedHashMap - Example Tutorial


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 result 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 type 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 e.g. 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 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 with your friends and colleagues. If you have any question or feedback then please drop me a note.

2 comments:

  1. Hi.. how to convert to map without using Java 8 if the list contains duplicate

    ReplyDelete
    Replies
    1. Hello 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.

      Delete