Quantcast
Viewing all articles
Browse latest Browse all 4681

Java 8 Collectors toMap

1. Introduction

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

For all the examples covered in 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.

Our Book class is defined as:

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 it works with:

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

3. Solving Key Conflicts

The example above worked well, but what would happen if there’s 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 can we return different Map implementations? The answer is yes:

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 as above 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));
}

Let’s 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 need to sort a list and use a TreeMap as a mapSupplier parameter:

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

The code above will sort the list based on the book name and then collect the results to a TreeMap:

@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 on GitHub.

Image may be NSFW.
Clik here to view.

Viewing all articles
Browse latest Browse all 4681

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>