In this short tutorial we will look at how to use a collector to create a map from a stream. Specifically we will use the Collectors.toMap
method. The most common form of this function has the signature.
1 2 3 | <T,K,U> Collector<T,?,Map<K,U>> toMap(
Function<? super T,? extends K> keyMapper,
Function<? super T,? extends U> valueMapper)
|
This method accepts a keyMapper
function that populates the keys of the created map. It also takes a valueMapper
function that populates the values of the map.
Let’s look at a simple example that creates a Map<Integer,String>
from a list of strings. In particular the created map will have the string length as the key, and the string as the value.
1 2 3 4 5 6 7 8 9 10 11 12 | @Test
public void toMap() {
List<String> listOfString = List.of("hello", "Goodbye" );
Map<Integer, String> expectedMap = new HashMap<>();
expectedMap.put(5, "hello" );
expectedMap.put(7, "Goodbye" );
Map<Integer, String> map = listOfString.stream().collect(
Collectors.toMap(k -> k.length(), v -> v));
assertThat(map, is(expectedMap))
}
|
In the above code we use the keyMapper
function:
1 | k -> k.length()
|
To return the keys of the created map. We also use the valueMapper
function:
1 | v -> v
|
To return the value of the map which is the string itself.
The Collectors.toMap
function will throw an IllegalStateException if a duplicate key is added to the map. For example the following code will throw an IllegalStateException.
1 2 3 4 5 6 7 | @Test(expected = IllegalStateException.class)
public void toMap_duplicatesThrowsExceptions() {
List<String> listOfString = List.of("hello", "Goodbye", "Welcome" );
Map<Integer, String> map = listOfString.stream().collect(
Collectors.toMap(k -> k.length(), v -> v));
}
|
The above code throws an IllegalStateException as the strings Goodbye and Welcome have the same length.
To handle duplicates we have to tell the toMap method how to handle them. To achieve this we need to use a different version of the toMap method.
1 2 3 | Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction)
|
Above we can see this method accepts a mergeFunction
parameter. This function tells the toMap method what to do with the duplicate. For example, below we use this parameter to tell the toMap method to keep the existing entry for a duplicate, and ignore the new entry.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Test
public void toMap_duplicates() {
List<String> listOfString = List.of("hello", "Goodbye", "Welcome" );
Map<Integer, String> expectedMap = new HashMap<>();
expectedMap.put(5, "hello" );
expectedMap.put(7, "Goodbye" );
Map<Integer, String> map = listOfString.stream().collect(
Collectors.toMap(k -> k.length(), v -> v,
(existing,replacement) -> existing));
assertThat(map, is(expectedMap));
}
|
In the above code we ignore the duplicate using the merge function:
1 | (existing,replacement) -> existing)
|
Finally, we can use the Collectors.toMap
method to return a sorted Map. In order to achieve this we will use a TreeMap. Moreover, a TreeMap is sorted to the natural ordering of its keys.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @Test
public void toMap_sorted() {
List<String> listOfString = List.of("hello", "Goodbye", "Welcome" );
Map<Integer, String> expectedMap = new HashMap<>();
expectedMap.put(5, "hello" );
expectedMap.put(7, "Goodbye" );
TreeMap<Integer, String> map = listOfString.stream().collect(
Collectors.toMap(k -> k.length(), v -> v,
(existing,replacement) -> existing, TreeMap::new));
assertThat(map, is(expectedMap));
assertTrue(map.firstKey().equals(5));
}
|
In this tutorial we looked at how to collect the elements of a stream into Map. We also saw how to handle duplicate keys and how to convert a stream to a sorted map.