How to Convert a Stream to List, Set, Map and different types of collections.
In Java, Streams are powerful tools that allow you to process collections in a functional way. You can convert a Stream
into different types of collections like List
, Set
, or Map
using the Collectors
class. These conversions are useful when you want to collect the results of your stream operations into a collection.
1. Convert Stream to List
To convert a Stream
into a List
, you can use Collectors.toList()
. This is the most common and simplest way to collect elements from a stream into a list.
Example:
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamToListExample {
public static void main(String[] args) {
Stream<String> stream = Stream.of("Java", "Python", "C++", "JavaScript");
List<String> list = stream.collect(Collectors.toList());
System.out.println(list); // Output: [Java, Python, C++, JavaScript]
}
}
Here:
Stream.of("Java", "Python", "C++", "JavaScript")
creates a stream.collect(Collectors.toList())
collects the stream elements into aList
.
Here’s what’s happening:
- Stream to List Conversion: The
input.stream()
converts your collection (like aList
,Set
, etc.) into a stream. This stream can then be processed or transformed using various methods in the Stream API. - In this case, you’re using
.collect(Collectors.toList())
, which means you want to collect all the elements of the stream into aList
. - Order of Elements: The list will contain the elements in the same order they appear in the stream. So, if the stream was generated from a list of elements like
[1, 2, 3, 4]
, the resulting list will also maintain the order[1, 2, 3, 4]
. This is one of the key properties of aList
in Java — it preserves the order of insertion. - Duplicates Allowed: Since a
List
allows duplicate elements, you can have the same element appear multiple times. For instance, if your stream was[1, 2, 2, 3, 4, 4]
, the resulting list would be[1, 2, 2, 3, 4, 4]
, which contains duplicates.
Type Inference and List Implementation
- Type Inference: Java uses type inference in the lambda expressions to determine what type of collection (like
ArrayList
orLinkedList
) should be returned. In your case, you have specified that the result is aList
, so the Stream API infers this and uses the appropriate implementation behind the scenes. However, this doesn’t mean it always returns a specific type of list likeArrayList
orLinkedList
. The implementation of the list is not guaranteed, unless you specify the exact list type yourself. - In short, Java will figure out from the stream element (e.g.,
Integer
in this case) and the expected result type (List<Integer>
) which type of list to use.
Customizing List Implementation (Optional)
If you specifically want a certain type of list, like an ArrayList
or a LinkedList
, you can explicitly create one using a supplier in the collect()
method:
Example:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class CustomListExample {
public static void main(String[] args) {
List<Integer> input = List.of(1, 2, 3, 4);
// Convert to ArrayList explicitly
List<Integer> arrayList = input.stream()
.collect(Collectors.toCollection(ArrayList::new));
System.out.println(arrayList); // Output: [1, 2, 3, 4]
}
}
2. Convert Stream to Set
If you want to convert a Stream
to a Set
, you can use Collectors.toSet()
. A Set
automatically removes any duplicate values, so if your stream contains duplicate elements, they will not appear in the resulting Set
.
Example:
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamToSetExample {
public static void main(String[] args) {
Stream<String> stream = Stream.of("Java", "Python", "Java", "C++", "JavaScript");
Set<String> set = stream.collect(Collectors.toSet());
System.out.println(set); // Output: [Java, Python, C++, JavaScript]
}
}
Here:
- The stream contains the element
"Java"
twice. collect(Collectors.toSet())
collects the elements into aSet
, which removes duplicates.
3. Convert Stream to Map
To convert a Stream
to a Map
, you need to provide two arguments to Collectors.toMap()
:
- A function to extract the key.
- A function to extract the value.
This is useful when you want to map stream elements into key-value pairs.
Example:
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamToMapExample {
public static void main(String[] args) {
Stream<String> stream = Stream.of("Java", "Python", "C++", "JavaScript");
Map<Integer, String> map = stream.collect(Collectors.toMap(String::length, s -> s));
System.out.println(map); // Output: {4=Java, 6=Python, 3=C++, 10=JavaScript}
}
}
Here:
String::length
is used to get the length of the string as the key.s -> s
is used to map the string itself as the value.
In this case, the length of each string is the key, and the string itself is the value in the resulting Map
.
4. Additional Notes
- Duplicates in Map: If there are duplicate keys in the stream,
Collectors.toMap()
will throw an exception. You can handle this by providing a third argument to specify how to merge duplicate values. For example:
Map<Integer, String> mapWithMerge = stream.collect(
Collectors.toMap(
String::length,
s -> s,
(existing, replacement) -> existing)
);
- Parallel Streams: If you’re working with a large amount of data, you can use parallel streams to process the elements faster. Just replace
stream
withstream.parallel()
.
Code Example-
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 demonstrate converting a stream to
* various collection types such as List, Set, Map, and ConcurrentMap.
*/
public class Hello {
public static void main(String[] args) {
// New Stream of Strings
List<String> input = Arrays.asList("apple", "banana", "orange", "apple", "mango", "banana", "kiwi", "apple");
// 1. Stream to List
List<String> listOfStrings = input.stream()
.collect(Collectors.toList());
System.out.println("Stream to List: " + listOfStrings);
// Stream to ArrayList
ArrayList<String> aList = input.stream()
.collect(Collectors.toCollection(ArrayList::new));
System.out.println("Stream to ArrayList: " + aList);
// Stream to LinkedList
LinkedList<String> linkedList = input.stream()
.collect(Collectors.toCollection(LinkedList::new));
System.out.println("Stream to LinkedList: " + linkedList);
// 2. Stream to Set
Set<String> aSet = input.stream()
.collect(Collectors.toSet());
System.out.println("Stream to Set: " + aSet);
// Stream to HashSet
HashSet<String> anHashSet = input.stream()
.collect(Collectors.toCollection(HashSet::new));
System.out.println("Stream to HashSet: " + anHashSet);
// Stream to LinkedHashSet (preserves insertion order)
LinkedHashSet<String> aLinkedHashSet = input.stream()
.collect(Collectors.toCollection(LinkedHashSet::new));
System.out.println("Stream to LinkedHashSet: " + aLinkedHashSet);
// 3. Stream to Map
Map<String, Integer> aMap = input.stream()
.collect(Collectors.toMap(Function.identity(),
String::length, (k1, k2) -> k1));
System.out.println("Stream to Map: " + aMap);
// Stream to HashMap
HashMap<String, Integer> anHashMap = input.stream()
.collect(Collectors.toMap (Function.identity(),
String::length, (k1, k2) -> k1, HashMap::new));
System.out.println("Stream to HashMap: " + anHashMap);
// Stream to LinkedHashMap (preserves insertion order)
LinkedHashMap<String, Integer> aLinkedHashMap = input.stream()
.collect(Collectors.toMap(Function.identity(),
String::length, (k1, k2) -> k1, LinkedHashMap::new));
System.out.println("Stream to LinkedHashMap: " + aLinkedHashMap);
// 4. Stream to ConcurrentMap (uses parallel stream)
ConcurrentMap<String, Integer> aConcurrentMap = input.parallelStream()
.collect(Collectors.toConcurrentMap(Function.identity(),
String::length, (k1, k2) -> k1));
System.out.println("Stream to ConcurrentMap: " + aConcurrentMap);
// Stream to ConcurrentHashMap
ConcurrentHashMap<String, Integer> aConcurrentHashMap = input.parallelStream()
.collect(Collectors.toConcurrentMap(Function.identity(),
String::length, (k1, k2) -> k1, ConcurrentHashMap::new));
System.out.println("Stream to ConcurrentHashMap: " + aConcurrentHashMap);
}
}
---------------OUTPUT-----------------------
Stream to List: [apple, banana, orange, apple, mango, banana, kiwi, apple]
Stream to ArrayList: [apple, banana, orange, apple, mango, banana, kiwi, apple]
Stream to LinkedList: [apple, banana, orange, apple, mango, banana, kiwi, apple]
Stream to Set: [banana, apple, orange, kiwi, mango]
Stream to HashSet: [banana, apple, orange, kiwi, mango]
Stream to LinkedHashSet: [apple, banana, orange, mango, kiwi]
Stream to Map: {apple=5, banana=6, orange=6, mango=5, kiwi=4}
Stream to HashMap: {apple=5, banana=6, orange=6, mango=5, kiwi=4}
Stream to LinkedHashMap: {apple=5, banana=6, orange=6, mango=5, kiwi=4}
Stream to ConcurrentMap: {apple=5, banana=6, orange=6, mango=5, kiwi=4}
Stream to ConcurrentHashMap: {apple=5, banana=6, orange=6, mango=5, kiwi=4}
Explanation:
- Converting Stream to Various Collections:
- Stream to List: The
collect(Collectors.toList())
method collects the elements of the stream into aList
. - Stream to ArrayList: Using
Collectors.toCollection(ArrayList::new)
to collect into anArrayList
. - Stream to LinkedList: Similarly,
Collectors.toCollection(LinkedList::new)
collects the stream into aLinkedList
. - Stream to Set: The
Collectors.toSet()
method collects the stream elements into aSet
, automatically removing duplicates. - Stream to HashSet: Using
Collectors.toCollection(HashSet::new)
to collect the stream into aHashSet
. - Stream to LinkedHashSet: The
LinkedHashSet
preserves the insertion order. We collect usingCollectors.toCollection(LinkedHashSet::new)
.
2.. Stream to Map:
- Stream to Map: This is an example of converting a stream to a map where the key is the string itself, and the value is the string’s length. The merge function
(k1, k2) -> k1
ensures that if there are duplicate keys (i.e., same string), we retain the first key encountered. - Stream to HashMap: The
Collectors.toMap()
withHashMap::new
creates aHashMap
. - Stream to LinkedHashMap: By specifying
LinkedHashMap::new
, theLinkedHashMap
is created which preserves the insertion order.
3. Stream to ConcurrentMap:
- Stream to ConcurrentMap: We use
parallelStream()
to process the stream in parallel, andCollectors.toConcurrentMap()
collects the result into a concurrent map. - Stream to ConcurrentHashMap: Similar to
ConcurrentMap
, but specifyingConcurrentHashMap::new
creates aConcurrentHashMap
.
Tricky interview questions and answers based on converting a stream to List
, Set
, and Map
in Java
1. Question: What happens if there are duplicate keys while converting a stream to a Map
? How do you handle it?
Answer: When you convert a stream to a Map
using Collectors.toMap()
, if there are duplicate keys in the stream, it will throw an IllegalStateException
. This is because Map
does not allow duplicate keys.
To handle this, you can provide a merge function as the third argument to Collectors.toMap()
. The merge function specifies how to resolve conflicts when there are duplicate keys.
Example:
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class DuplicateKeysHandling {
public static void main(String[] args) {
Stream<String> stream = Stream.of("Java", "Python", "Java", "C++");
// Handling duplicate keys using a merge function
Map<Integer, String> map = stream.collect(
Collectors.toMap(
String::length, // Key is string length
s -> s, // Value is the string itself
(existing, replacement) -> existing // Keep existing value on conflict
)
);
System.out.println(map); // Output: {4=Java, 6=Python}
}
}
In this case, when there’s a key conflict (duplicate length 4 for both "Java"
and "Java"
), the merge function decides to keep the first encountered value (existing
).
2. Question: Can you convert an infinite stream to a List
, Set
, or Map
? If yes, how?
Answer: No, you cannot directly collect an infinite stream into a List
, Set
, or Map
because these collections have finite sizes, and an infinite stream has no end. Attempting to do so will result in an infinite loop, leading to memory overflow or a StackOverflowError
.
However, you can limit the stream using stream.limit(n)
to process a finite number of elements. For example, to convert the first 100 elements of an infinite stream into a List
:
Example:
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class InfiniteStreamExample {
public static void main(String[] args) {
Stream<Integer> infiniteStream = Stream.iterate(1, n -> n + 1); // Infinite stream of integers
// Convert first 100 elements to List
List<Integer> list = infiniteStream.limit(100).collect(Collectors.toList());
System.out.println(list.size()); // Output: 100
}
}
In this example, stream.limit(100)
limits the infinite stream to the first 100 elements.
3. Question: How does Collectors.toMap()
behave if the stream contains elements with null keys or null values?
Answer: The Collectors.toMap()
method does not allow null keys or null values. If you try to collect elements with null keys or null values, a NullPointerException
will be thrown.
Example:
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class NullKeyValueExample {
public static void main(String[] args) {
Stream<String> stream = Stream.of("Java", "Python", "C++", null);
// This will throw a NullPointerException
Map<Integer, String> map = stream.collect(Collectors.toMap(String::length, s -> s));
}
}
- In this example, the null value will cause a
NullPointerException
.
To safely handle null values, you can filter them before applying toMap()
.
4. Question: How would you convert a stream to a Map
where the key is the element itself and the value is the element’s length?
Answer: You can use Collectors.toMap()
to convert a stream to a map, with the element itself as the key and its length as the value.
Example:
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ConvertStreamToMapWithKeyAndLength {
public static void main(String[] args) {
Stream<String> stream = Stream.of("Java", "Python", "C++", "JavaScript");
// Convert stream to Map, key is the string and value is the string length
Map<String, Integer> map = stream.collect(Collectors.toMap(s -> s, String::length));
System.out.println(map); // Output: {Java=4, Python=6, C++=3, JavaScript=10}
}
}
The key is the string itself (s -> s
), and the value is the length of the string (String::length
).
5. Question: What will happen if the stream has duplicate elements while collecting it into a Set
?
Answer: If a stream has duplicate elements and you collect it into a Set
using Collectors.toSet()
, the Set
will automatically remove the duplicates. A Set
only allows unique elements, so it will store only one instance of each duplicate element.
Example:
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamToSetWithDuplicates {
public static void main(String[] args) {
Stream<String> stream = Stream.of("Java", "Python", "Java", "C++");
// Convert stream to Set
Set<String> set = stream.collect(Collectors.toSet());
System.out.println(set); // Output: [Java, Python, C++]
}
}
The duplicate "Java"
is removed automatically because Set
does not allow duplicates.
6. Question: Can you convert a stream to a Map
using the stream's index as the key?
Answer: Yes, you can convert a stream into a Map
where the index of each element in the stream is used as the key. This can be achieved by using IntStream.range()
to generate indices and then using the Collectors.toMap()
method.
Example:
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class ConvertStreamToMapWithIndex {
public static void main(String[] args) {
Stream<String> stream = Stream.of("Java", "Python", "C++", "JavaScript");
// Convert stream to Map with index as key and element as value
Map<Integer, String> map = IntStream.range(0, (int) stream.count()) // Generate indices
.boxed()
.collect(Collectors.toMap(i -> i, i -> stream.skip(i).findFirst().get()));
System.out.println(map); // Output: {0=Java, 1=Python, 2=C++, 3=JavaScript}
}
}
IntStream.range(0, n)
generates indices for each element.stream.skip(i).findFirst().get()
retrieves the element at that index.