Java 8 Collectors toMap

1. Overview

In this quick tutorial, we’re going to talk about the toMap() method of the Collectors class. We’ll use it to collect Streams into a Map instance.

For all the examples covered here, we’ll use a list of books as a starting point and transform it into different Map implementations.

2. List to Map

We’ll start with the simplest case, by transforming a List into a Map.

Here is how we define our Book class:

class Book {
    private String name;
    private int releaseYear;
    private String isbn;
    
    // getters and setters
}

And we’ll create a list of books to validate our code:

List<Book> bookList = new ArrayList<>();
bookList.add(new Book("The Fellowship of the Ring", 1954, "0395489318"));
bookList.add(new Book("The Two Towers", 1954, "0345339711"));
bookList.add(new Book("The Return of the King", 1955, "0618129111"));

For this scenario we’ll use the following overload of the toMap() method:

Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
  Function<? super T, ? extends U> valueMapper)

With toMap, we can indicate strategies for how to get the key and value for the map:

public Map<String, String> listToMap(List<Book> books) {
    return books.stream().collect(Collectors.toMap(Book::getIsbn, Book::getName));
}

And we can easily validate that it works:

@Test
public void whenConvertFromListToMap() {
    assertTrue(convertToMap.listToMap(bookList).size() == 3);
}

3. Solving Key Conflicts

The example above worked well, but what would happen with a duplicate key?

Let’s imagine that we keyed our Map by each Book‘s release year:

public Map<Integer, Book> listToMapWithDupKeyError(List<Book> books) {
    return books.stream().collect(
      Collectors.toMap(Book::getReleaseYear, Function.identity()));
}

Given our earlier list of books, we’d see an IllegalStateException:

@Test(expected = IllegalStateException.class)
public void whenMapHasDuplicateKey_without_merge_function_then_runtime_exception() {
    convertToMap.listToMapWithDupKeyError(bookList);
}

To resolve it, we need to use a different method with an additional parameter, the mergeFunction:

Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
  Function<? super T, ? extends U> valueMapper,
  BinaryOperator<U> mergeFunction)

Let’s introduce a merge function that indicates that, in the case of a collision, we keep the existing entry:

public Map<Integer, Book> listToMapWithDupKey(List<Book> books) {
    return books.stream().collect(Collectors.toMap(Book::getReleaseYear, Function.identity(),
      (existing, replacement) -> existing));
}

Or in other words, we get first-win behavior:

@Test
public void whenMapHasDuplicateKeyThenMergeFunctionHandlesCollision() {
    Map<Integer, Book> booksByYear = convertToMap.listToMapWithDupKey(bookList);
    assertEquals(2, booksByYear.size());
    assertEquals("0395489318", booksByYear.get(1954).getIsbn());
}

4. Other Map Types

By default, a toMap() method will return a HashMap.

But we can return different Map implementations:

Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
  Function<? super T, ? extends U> valueMapper,
  BinaryOperator<U> mergeFunction,
  Supplier<M> mapSupplier)

where the mapSupplier is a function that returns a new, empty Map with the results.

4.1. List to ConcurrentMap

Let’s take the same example and add a mapSupplier function to return a ConcurrentHashMap:

public Map<Integer, Book> listToConcurrentMap(List<Book> books) {
    return books.stream().collect(Collectors.toMap(Book::getReleaseYear, Function.identity(),
      (o1, o2) -> o1, ConcurrentHashMap::new));
}

We’ll go on and test our code:

@Test
public void whenCreateConcurrentHashMap() {
    assertTrue(convertToMap.listToConcurrentMap(bookList) instanceof ConcurrentHashMap);
}

4.2. Sorted Map

Lastly, let’s see how to return a sorted map. For that, we’ll use a TreeMap as a mapSupplier parameter.

Because a TreeMap is sorted according to the natural ordering of its keys by default, we don’t have to explicitly sort the books ourselves:

public TreeMap<String, Book> listToSortedMap(List<Book> books) {
    return books.stream() 
      .collect(
        Collectors.toMap(Book::getName, Function.identity(), (o1, o2) -> o1, TreeMap::new));
}

So in our case, the returned TreeMap will be sorted in alphabetical order by the book name:

@Test
public void whenMapisSorted() {
    assertTrue(convertToMap.listToSortedMap(bookList).firstKey().equals(
      "The Fellowship of the Ring"));
}

5. Conclusion

In this article, we looked into the toMap() method of the Collectors class. It allows us to create a new Map from a Stream.

We also learned how to resolve key conflicts and create different map implementations.

As always, the code is available over on GitHub.

Related posts:

A Guide to LinkedHashMap in Java
Spring Boot - Servlet Filter
Java Program to Implement SimpeBindings API
Java Program to Find MST (Minimum Spanning Tree) using Kruskal’s Algorithm
Finding the Differences Between Two Lists in Java
Spring Cloud – Securing Services
Hướng dẫn Java Design Pattern – Command
LinkedHashSet trong Java hoạt động như thế nào?
Logout in an OAuth Secured Application
A Guide to HashSet in Java
Immutable Map Implementations in Java
Java Program to Generate All Possible Combinations Out of a, b, c, d, e
Spring Autowiring of Generic Types
Java Program to Implement First Fit Decreasing for 1-D Objects and M Bins
Java Program to Perform Quick Sort on Large Number of Elements
OAuth2 for a Spring REST API – Handle the Refresh Token in Angular
Java Program to Perform Searching in a 2-Dimension K-D Tree
Java Program to Check Whether it is Weakly Connected or Strongly Connected for a Directed Graph
Converting String to Stream of chars
Lập trình đa luồng với CompletableFuture trong Java 8
Anonymous Classes in Java
Java Program to Implement Aho-Corasick Algorithm for String Matching
How to Get All Dates Between Two Dates?
Java Program to Generate Randomized Sequence of Given Range of Numbers
Java Program to Find Path Between Two Nodes in a Graph
Spring Boot Annotations
Partition a List in Java
Lập trình mạng với java
@DynamicUpdate with Spring Data JPA
Java Program to find the maximum subarray sum using Binary Search approach
Java Program to Implement Interval Tree
Redirect to Different Pages after Login with Spring Security