Thursday, May 12, 2022

How to Convert a Stream to List, Set, and Map in Java? Example Tutorial

Hello guys, if you are wondering how to convert a Java Stream to Java Collections like List, Set and Map then you have come to the right place. Earlier, I have shared free Java Courses for beginners and in this article, I am going to share examples to convert Stream to ArrayList, LinkedList, HashSet, TreeSet, LinkedHashSet, TreeMap, HashMap, and ConcurrentHashMap in Java. These are easy-to-follow examples and suitable for both beginners and experienced Java programmers.  In Java 8, Stream is one of the most important classes as it allows a lot of useful functional operations like filter, map, flatmap, etc on a collection of objects. 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 a Java designer by directly adding a stream() method on the Collection interface, which means all other interfaces and classes derived from Stream also have this method. 

Though the second problem of converting a Stream to List, Set, and Map requires some understanding of the Stream API of JDK 8 and that's what you will learn in this article.

I'll show you step-by-step examples but the most important thing to learn is that the 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 the 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 first go through a comprehensive Java course like The Complete Java Masterclass course to start on the right note. It's also one of the most up-to-date and comprehensive courses.





How to convert Stream to List, Set, and HashMap in Java 8

Ok, So without wasting any more time, let's start with the work. Here is my list of step-by-step examples to convert Javaa Stream to ArrayList, HashSet, and HashMap in Java. 

1. Stream to List Example

You can use the toList() method of the Collectors class to convert a Stream to List in Java 8. Here is an example of 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 like 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 as the type of result expected.


1.1 Stream to ArrayList Example

In the previous example, we see that the toList() method returns 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 like ArrayList. In that case, you need to use the toCollection() method of the Collectors class.

This method accepts a Supplier that 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 like Vector, you can also pass Vector::new. If you are wondering what is this code called then don't worry, it's known as a constructor reference, similar to a method reference.


1.2 Stream to LinkedList Example

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 Learn Java Functional Programming with Lambdas & Streams course to learn more about Stream and Functional Programming in Java 8.

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



2. Stream to Set Examples

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 the collect method here. You can't assume it a HashSet, but it will something which implements the 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 Example

If you need a HashSet rather than a Set, 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 the order of elements will be gone.



2.2 Stream to LinkedHashSet Example

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 exist 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 Example

Similar to earlier examples, you can also use the toCollection() method of the 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 differ 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 in learning more about different collection classes and working 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 Examples

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 that 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 like 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 Example

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, the third is the conflict resolver in case of the duplicate key, and the 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 Example

You can convert 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 latter keep the mapping in the order they are inserted while HashMap doesn't guarantee any order.


3.3 Stream to TreeMap Example

The same method we have used to convert Stream to HashMap and LinkedHashMap can convert Stream to TreeMap as well. Instead of passing LinkedHashMap:new just pass the TreeMap::new to the last argument of the 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 that 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 to learn more about Collectors and other Stream methods.

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




4. Stream to ConcurrentMap Examples

Java 8 Stream API also allows you to convert a parallel Stream into ConcurrentHashMap. Like toMap(), the Collectors class also has 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 the result of the parallel stream and need a thread-safe hash table data structure then it's better to use a ConcurrentMap.


4.1 Java Stream to ConcurrentHashMap Example

Since ConcurrentHashMap is the most popular implementation of the 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 the 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, the third argument is to ignore duplicate key and the fourth argument specifies 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.




How to Convert Stream to List, Set, and Map in Java - Example

Our complete Java program demonstrates how you can use the 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 with 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}
 
 
 

Important Things about Stream, List, Set, and Map

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 like

1. I have used a List as input instead of creating a Stream like with the 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, the 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 the 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.


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
  • 5 Free Courses to learn Java 8 and 9 (courses)
  • 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)
  • How to format/parse the date with LocalDateTime in Java 8? (tutorial)
  • How to use peek() method in Java 8 (example)
  • What is the default method in Java 8? (example)
  • How to use Stream class in Java 8 (tutorial)
  • How to sort the may by values in Java 8? (example)
  • Difference between abstract class and interface in Java 8? (answer)
  • Difference between Map and FlatMap in Java 8 (answer)
  • How to sort the map by keys in Java 8? (example)
  • How to join String in Java 8 (example)

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

P. S. - If you are new to Java 8 and Functional Programming and looking for some free online courses to learn new concepts and features introduced in Java 8, then you can also check out this Java 8 Functional Programming: Lambda Expressions Quickly course on Udemy. It's completely free and more than 10,000 Java developers have joined this course. 

2 comments:

  1. Hi

    could you please help me with the below scenario .how can we get the list of DOJs using stream

    i have a list of employee as input .employee has name id and doj(Date of joining) as fields . i need List with distinct DOJ values .

    ReplyDelete
  2. just filter it like this list.stream().filter( e1, e2 - e1.doj != e2.doj)

    ReplyDelete

Feel free to comment, ask questions if you have any doubt.