How to convert Stream to List, Set, Map, and ConcurrentHashMap in Java 8 - Learn with Examples

In Java 8, Stream is one of the most important class as it allows a lot of useful functional operations e.g. filter, map, flatmap, etc on a collection of object. Hence, going forward converting a Collection to Stream, performing operations and then converting the result back to different Collection classes like List, Set, and Map will be a common task. The first problem of converting Stream to Collection is solved by Java designer by directly adding a stream() method on Collection interface, which means all other interfaces and classes which derived from Stream also have this method. Though the second problem of converting a Stream to List, Set, and Map requires some understanding of Stream API of JDK 8 and that's what you will learn in this article.

I'll show you step by step example but the most important thing to learn is that Collector interface can be used to collect the result of Stream to any collection classes like List, Set, Map, and even ConcurrentMap.

It works with a companion collect() method from Stream class. This method accepts a Collector and returns the Collection you want.  Btw, if you are just starting with Java programming then I suggest you to first go through a comprehensive Java course like The Complete Java Masterclass course on Udemy to start on the right note. It's also one of the most up-to-date and comprehensive course.

Ok, So without wasting any more time, let's start with the work.





1. Stream to List

You can use the toList() method of Collectors class to convert a Stream to List in Java 8. Here is an example for converting a stream of integers to a list of integers. The stream is from another list of Integers.

List<Integer> listOfIntegers 
          = input.stream().collect(Collectors.toList());

The list will contain the items or elements in the same order they appear in Stream and duplicates are also allowed. Though the list implementation is not guaranteed e.g. you can't expect an ArrayList or LinkedList, though if you want that can be done and we'll see it shortly.

Btw, if you are wondering how does the collect method know which type of List to return then don't worry, they figured it out using excellent type inference available to lambda expressions. In short, they know it from the Stream element as well type of the result expected.


1.1 Stream to ArrayList

In the previous example, we see that toList() method return a List implementation but there is no guarantee over implementation. This may work for most of the cases but in many other cases, you need concrete implementations e.g. ArrayList. In that case, you need to use the toCollection() method of Collectors class.

This method accepts a Supplier which returns a new, empty Collection of the appropriate type.

ArrayList<Integer> aList 
        = input.stream()
               .collect(Collectors.toCollection(ArrayList::new));

You can see that we have passed ArrayList::new to the collection() method because we wanted to accumulate the result of Stream into an ArrayList. If you want any other Collection e.g. Vector, you can also pass Vector::new. If you are wondering what is this code called then don't worry, it's known as constructor reference, similar to a method reference.


1.2 Stream to LinkedList

This is quite similar to the previous example. Instead of ArrayList we have converted Stream into a LinkedList and there is only one change required in the previous code, instead of ArrayList::new we have passed LinkedList::new to the toCollection() method.

LinkedList<Integer> linkedList 
           = input.stream()
                  .collect(Collectors.toCollection(LinkedList::new));

The returned LinkedList will contain all the elements of Stream in the same order they appear in Stream.  You can further see The Complete Java Masterclass course to learn more about Stream in Java 8.

How to convert Stream to List, Set, Map, and ConcurrentHashMap in Java 8 - Learn with Examples



2. Stream to Set

You can convert a Stream to Set by using the Collectors.toSet() method. Since Set doesn't allow duplicates and doesn't provide any ordering guarantee, any duplicate elements from Stream are lost and ordering is also gone.

Set<Integer> aSet = input.stream().collect(Collectors.toSet());

There are no implementation guarantees provided for the Set returned by collect method here. You can't assume it a HashSet, but it will something which implements Set interface. Also, the size of Set will be less than or equal to a number of elements in the final Stream because duplicate elements are lost when you covert Stream to Set.


2.1 Stream to HashSet

If you need a HashSet rather than a Set then you need to use the toCollection() method rather than the toSet() method. As we have seen in the case of ArrayList, the toCollection() method allows you to specify which type of Collection class you want by providing a supplier. Here is an example for converting Stream to HashSet in Java.

HashSet<Integer> anHashSet
           = input.stream()
                  .collect(Collectors.toCollection(HashSet::new));

As with any other Set, when you convert Stream to HashSet, all duplicate elements will be lost and order of elements will be gone.



2.2 Stream to LinkedHashSet

Similar to HashSet, you can also use toCollection() method of Collectors class to convert a Stream to LinkedHashSet in Java. The main difference between HashSet and LinkedHashSet is that order is preserved. Elements in the LinkedHashSet exists in the order they are inserted. Btw, If you are interested in learning more differences you can see my earlier article HashSet vs LinkedHashSet in Java.

LinkedHashSet<Integer> aLinkedHashSet
              = input.stream()
                     .collect(Collectors.toCollection(LinkedHashSet::new));

Again, like any other Set, duplicate elements from Stream will be lost, hence the size of the LinkedHashSet will be less than or equal to the number of elements in the final Stream.


2.3 Stream to TreeSet

Similar to earlier examples, you can also use the toCollection() method of Collectors class to convert a Stream to TreeSet in Java. Though, you should remember that TreeSet is a sorted Set and keeps the elements in their natural order or a custom order specified by Comparator.

TreeSet<Integer> aTreeSet 
        = input.stream()
                .collect(Collectors.toCollection(TreeSet::new));

In this example, elements in the TreeSet will be in their sorted order which may be different from their order in Stream. Again, any duplicate element will be lost because TreeSet doesn't allow duplicate and hence the size of TreeSet will be less than or equal to the size of the final Stream.

If you are interested to learn more about different collection classes and how to work with them using Stream, see From Collections to Streams in Java 8 Using Lambda Expressions course on Pluralsight.

 Stream to TreeSet, HashSet, and LinkedHashSet in Java



3. Stream to Map

In Java 8, you can also convert a Stream to Map by using the Collectors.toMap() method. This method accepts a key and value mapper to create a Map from the objects of Stream. For example, if your Stream contains String then you can create a Map which maps String with their length.

Since Map needs two object key and value, we need to provide keyMapper and valueMapper as shown in the following example:

Map<Integer, String> aMap 
           = input.stream()
                  .collect(Collectors.toMap(Function.identity(),
                                            String::valueOf,
                                            (k1, k2) -> k1));

There are a couple of more challenges e.g. Stream can contain duplicates but Map doesn't allow duplicate keys. Hence, you also need to provide a conflict resolver, there is an overloaded toMap() method for that. For more details, you can see my earlier post about converting Stream to Map in Java 8.


3.1 Stream to HashMap

If you need HashMap instead of just Map then you need to use the overloaded version of toMap() which accepts four arguments. The first two are key and value mapper, third is the conflict resolver in case of duplicate key and fourth accept a Supplier to create the type of Map you want. This is similar to the toCollection() method which we have used earlier to convert Stream to ArrayList and HashSet.

HashMap<Integer, String> anHashMap 
        = input.stream()
               .collect(Collectors.toMap(Function.identity(),
                                         String::valueOf, 
                                         (k1, k2) -> k1,
                                          HashMap::new));
 
 
In this example, we have used Function.identity() to use the same element as key in the Map and String::valueOf to convert Integer to String as we are creating a Map of String to Integer.



3.2 Stream to LinkedHashMap

You can covert a Stream to LinkedHashMap by using the same toMap() method which we have used to convert Stream to HashMap in the previous example. The only change is that instead of passing HashMap::new to the fourth parameter we'll pass LinkedHashMap::new as shown in the following example:

LinkedHashMap<Integer, String> aLinkedHashMap
         = input.stream()
                .collect(Collectors.toMap(Function.identity(),
                                          String::valueOf, (k1, k2) -> k1, 
                                          LinkedHashMap::new));

The key difference between a HashMap and LinkedHashMap is that the later keep the mapping in the order they are inserted while HashMap doesn't guarantee any order.


3.3 Stream to TreeMap

The same method which we have used to convert Stream to HashMap and LinkedHashMap can be used to convert Stream to TreeMap as well. Instead of passing LinkedHashMap:new just pass the TreeMap::new to the last argument of toMap() method.

TreeMap<Integer, String> aTreeMap
       = input.stream()
              .collect(Collectors.toMap(Function.identity(),
                                        String::valueOf, (k1, k2) -> k1, 
                                        TreeMap::new));

Remember, TreeMap is a sorted Map which keeps its keys in a sorted order specified by Comparable or Comparator. In our case String keys will be sorted in their natural order.  See a comprehensive Java 8 book like Java 8 in Action in to learn more about Collectors and other Stream methods.

Converting Stream to Map, TreeMap, hashMap, and LinkedHashMap in Java 8




4. Stream to ConcurrentMap

Java 8 Stream API also allows you to convert a parallel Stream into ConcurrentHashMap. Like toMap(), Collectors class also have a toConcurrentMap() method which takes a key and value mapper to convert a parallel Stream to ConcurrentHashMap. The syntax of toConcurrentMap() is quite similar to the toMap() method which we have seen in the previous example. Once you understand that, it's easy to use this method as well.

Here is an example for converting Stream to ConcurrentMap in Java

ConcurrentMap<Integer, String> aConcurrentMap 
           = input.parallelStream()
                  .collect(Collectors.toConcurrentMap(Function.identity(),
                                                      String::valueOf,
                                                      (k1, k2) -> k1));

The rule of thumb is that if you are collecting result of the parallel stream and need a thread-safe hash table data structure than it's better to use a ConcurrentMap.


4.1 Stream to ConcurrentHashMap

Since ConcurrentHashMap is the most popular implementation of ConcurrentMap interface, more often than not you will need to convert parallel Stream to ConcurrentHashMap just like we converted Stream to HashMap.

The process is exactly same, just use the overloaded toConcurrentMap() method which takes four parameters and provides a constructor reference like ConcurrentHashMap::new to collect results in a ConcurrentHashMap.

ConcurrentHashMap<Integer, String> aConcurrentHashMap 
         = input.parallelStream()
                .collect(Collectors.toConcurrentMap(
                                      Function.identity(), 
                                      String::valueOf, (k1, k2) -> k1, 
                                      ConcurrentHashMap::new));
 
In this example, the first argument is Function.identity() which we are using as the key mapper and this stores the object itself as key. The second argument is String::valueOf to convert Integer to String, third argument is to ignore duplicate key and the fourth argument specify that we need ConcurrentHashMap.

Btw, if you are interested in learning other Java 8 features apart from lambdas and stream like Date and Time API, Optionals, etc then you can also check out this nice little course What's New in Java 8 from Pluralsight.




An Example to Convert Stream to List, Set, Map and ConcurentMap in Java 

Here is our complete Java program to demonstrate how you can use Collectors class to convert a Stream of values into List, Set, Map and ConcurrentMap in Java. Not only you will learn to convert Stream to generic Collection classes but also specific ones like ArrayList, LinkedList, HashMap, HashSet, LinkedHashMap, and TreeMap. You can just copy paste and run this program in your Eclipse IDE and play around it to understand how various methods work.
package tool;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import java.util.stream.Collectors;
 
 
/**
* 
* A simple Java Program to convert a String to 
* short primitive or Short wrapper object in Java.
*/
public class Hello {
 
public static void main(String[] args) {
 
// Stream of Integers
List<Integer> input = Arrays.asList(1, 2, 3, 4, 5, 6, 78, 9, 10, 3, 2, 34, 5, 3); 
 
// 1. Stream to List
List<Integer> listOfIntegers = input.stream()
                                    .collect(Collectors.toList());
System.out.println("Stream to List: " + listOfIntegers);
 
// Stream to ArrayList
ArrayList<Integer> aList = input.stream()
                                .collect(Collectors.toCollection(ArrayList::new));
System.out.println("Stream to ArrayList: " + aList);
 
 
// Stream to LinkedList
LinkedList<Integer> linkedList = input.stream()
                                      .collect(Collectors.toCollection(LinkedList::new));
System.out.println("Stream to LinkedList: " + linkedList);
 
 
// 2. Stream to Set
Set<Integer> aSet = input.stream().collect(Collectors.toSet());
System.out.println("Stream to Set: " + aSet);
 
// Stream to HashSet
HashSet<Integer> anHashSet = input.stream()
                                  .collect(Collectors.toCollection(HashSet::new));
System.out.println("Stream to HashSet: " + anHashSet);
 
// Stream to LinkedHashSet
LinkedHashSet<Integer> aLinkedHashSet = input.stream()
                                       .collect(Collectors.toCollection(LinkedHashSet::new));
System.out.println("Stream to LinkedHashSet: " + aLinkedHashSet);
 
 
// 3. Stream to Map
Map<Integer, String> aMap = input.stream()
                                 .collect(Collectors.toMap(
                                      Function.identity(), String::valueOf, (k1, k2) -> k1));
System.out.println("Stream to Map: " + aMap);
 
// Stream to HashMap
HashMap<Integer, String> anHashMap = input.stream()
                                          .collect(Collectors.toMap(
                       Function.identity(), String::valueOf, (k1, k2) -> k1, HashMap::new));
System.out.println("Stream to HashMap: " + anHashMap);
 
// Stream to LinkedHashMap
LinkedHashMap<Integer, String> aLinkedHashMap
               = input.stream()
                      .collect(Collectors.toMap(
                 Function.identity(), String::valueOf, (k1, k2) -> k1, LinkedHashMap::new));
System.out.println("Stream to LinkedHashMap: " + aLinkedHashMap);
 
 
// 4. Stream to ConcurrentMap
ConcurrentMap<Integer, String> aConcurrentMap
         = input.parallelStream()
                .collect(Collectors.toConcurrentMap(
                   Function.identity(), String::valueOf, (k1, k2) -> k1));
System.out.println("Stream to ConcurrentMap: " + aConcurrentMap);
 
// Stream to ConcurrentHashMap
ConcurrentHashMap<Integer, String> aConcurrentHashMap
       = input.parallelStream()
            .collect(Collectors.toConcurrentMap(
              Function.identity(), String::valueOf, (k1, k2) -> k1, ConcurrentHashMap::new));
System.out.println("Stream to ConcurrentHashMap: " + aConcurrentHashMap);
 
}
 
}
 
Output
Stream to List: [1, 2, 3, 4, 5, 6, 78, 9, 10, 3, 2, 34, 5, 3]
Stream to ArrayList: [1, 2, 3, 4, 5, 6, 78, 9, 10, 3, 2, 34, 5, 3]
Stream to LinkedList: [1, 2, 3, 4, 5, 6, 78, 9, 10, 3, 2, 34, 5, 3]
Stream to Set: [1, 2, 34, 3, 4, 5, 6, 9, 10, 78]
Stream to HashSet: [1, 2, 34, 3, 4, 5, 6, 9, 10, 78]
Stream to LinkedHashSet: [1, 2, 3, 4, 5, 6, 78, 9, 10, 34]
Stream to Map: {1=1, 34=34, 2=2, 3=3, 4=4, 5=5, 6=6, 9=9, 10=10, 78=78}
Stream to HashMap: {1=1, 34=34, 2=2, 3=3, 4=4, 5=5, 6=6, 9=9, 10=10, 78=78}
Stream to LinkedHashMap: {1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 78=78, 9=9, 10=10, 34=34}
Stream to ConcurrentMap: {1=1, 34=34, 2=2, 3=3, 4=4, 5=5, 6=6, 9=9, 10=10, 78=78}
Stream to ConcurrentHashMap: {1=1, 34=34, 2=2, 3=3, 4=4, 5=5, 6=6, 9=9, 10=10, 78=78}
 
 
 

What Can You Learn From this Program

Even though I have already explained each part to you separately while converting Stream to List, Set, Map and various other collection classes, there are a lot of things you can still learn by understanding the output of this program.

If you look at this program and output there are a lot of useful insights and things to learn e.g.

1) I have used a List as input instead of creating a Stream e.g. with Stream.of() method. I have done that because I need to reuse the input, once you call the collect method on Stream, a terminal operation, stream will no longer usable.

2) When we have converted Stream to List, the order of an element is intact and they appear in the same order as they were in Stream. Also, duplicate values are preserved because List allows duplicates.

3) The Collectors.toList() method returns a List implementation not an ArrayList.

4) If you need ArrayList or LinkedList or any other specific implementation, you need to use Collectors.toCollection() method instead of Collectors.toList() or toSet().

5) When we converted Stream to Set, the order is lost because Set doesn't guarantee order and also duplicate values were removed.

6) When we converted Stream to LinkedHashSet, duplicates are gone but the order of elements in Stream is preserved.

7) When we converted Stream to Map, we need to pass two objects a key and value but Stream just contains one object, hence we need to provide a keyMapper and valueMapper. Also because our list contains duplicates and Map cannot have duplicate keys we need to provide a function to resolve key in case of duplicates.

8) The toMap() method of Collectors is overridden to provide any kind of Map e.g. HashMap or LinkedHahsMap.

9) When we have converted Stream to LinkedHashMap, the order of mapping is according to the order of elements in Stream.

10) There is a toConcurerntMap() method to convert a Stream to ConcurrentMap, which is also overloaded to generate any kind of ConcurrentMap like ConcurrentHashMap.


That's all about converting Stream to List, Set and Map in Java 8. It's not that difficult you just need to remember the Collectors class and its various methods. With Maps, it's a little bit difficult because you also need to handle duplicate keys, etc but as long as you remember the syntax and can understand functional arguments, it can be done without any issue. Btw, don't forget to static import Collectors class to make your code more concise.

Further Learning
The Complete Java MasterClass
The Ultimate Java 8 Tutorial
From Collections to Streams in Java 8 Using Lambda Expressions


Related Java 8 Tutorials
If you are interested in learning more about new features of Java 8, here are my earlier articles covering some of the important concepts of Java 8
  • Java 8 Interview Questions Preparation Course (free)
  • 5 Books to Learn Java 8 from Scratch (books)
  • 20 Examples of Date and Time in Java 8 (tutorial)
  • How to convert List to Map in Java 8 (solution)
  • What is the default method in Java 8? (example)
  • How to use Stream class in Java 8 (tutorial)
  • Difference between abstract class and interface in Java 8? (answer)
  • How to format/parse the date with LocalDateTime in Java 8? (tutorial)
  • How to use peek() method in Java 8 (example)
  • How to use filter() method in Java 8 (tutorial)
  • How to sort the map by keys in Java 8? (example)
  • How to sort the may by values in Java 8? (example)
  • How to join String in Java 8 (example)
  • 5 Free Courses to learn Java 8 and 9 (courses)

Thanks a lot for reading this article so far. If you like these Stream to Collection like List, Set and Map examples then please share with your friends. If you have any feedback or doubt then please drop a note.

P. S. - If you are looking for some free courses to learn new concepts and features introduced in Java 8 then you can also check out this list of Free Java 8 courses on FreeCodeCamp. 

No comments:

Post a Comment