Java 8 Collectors toMap – Java 8 采集器 toMap

最后修改: 2019年 6月 4日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

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.

在这个快速教程中,我们将讨论Collectors类的toMap()方法。我们将用它来收集Streams到一个Map实例。

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

在这里涉及的所有例子中,我们将使用一个书籍列表作为起点,并将其转化为不同的Map实现。

2. List to Map

2.从列表地图

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

我们将从最简单的情况开始,将List转换为Map

Here is how we define our Book class:

下面是我们如何定义我们的Book类。

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:

在这种情况下,我们将使用toMap()方法的以下重载。

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:

通过toMap,我们可以指出如何获得地图的键和值的策略

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

3.解决关键冲突

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:

鉴于我们之前的书单,我们会看到一个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:

为了解决这个问题,我们需要使用一个额外参数的不同方法,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

4.其他地图类型

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

默认情况下,toMap()方法将返回一个HashMap

But we can return different Map implementations:

但我们可以返回不同的Map实现

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.

其中mapSupplier是一个函数,返回一个新的、空的Map与结果。

4.1. List to ConcurrentMap

4.1.ListConcurrentMap

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

让我们以同样的例子,添加一个mapSupplier函数来返回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

4.2. 排序的地图

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

最后,让我们看看如何返回一个排序的地图。为此,我们将使用一个TreeMap作为mapSupplier参数。

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:

因为TreeMap默认是根据其键的自然排序来排序的,所以我们不必自己明确地对books进行排序。

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:

因此,在我们的案例中,返回的TreeMap将按书名的字母顺序排序。

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

5. Conclusion

5.总结

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

在这篇文章中,我们研究了Collectors类的toMap() 方法。它允许我们从Stream创建一个新的Map

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

我们还学习了如何解决关键冲突和创建不同的地图实现。

As always, the code is available over on GitHub.

像往常一样,代码可在GitHub上获得