Java 8 Collectors.toMap()方法使用教程

作者 | 2020年4月23日

1. 前言

在本文中将学习在Java中通过Collectors.topMap()方法将Stream转为Map对象。

在所以的例子中我们会使用一个Book对象来演示

2. List转Map

我们从一个简单的例子开始,把一个List转为Map。

先来看一下Book类的定义:

class Book {
    private String name;
    private int releaseYear;
    private String isbn;

    // getters and setters
}

然后,再创建一个Book的List对象:

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"));

在本例中,我们将使用下面这个toMap()方法的重载函数:

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

我们可以在toMap()方法上指定Key与Value的获取策略:

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

最终,我们编写一个单元测试来验证上述函数:

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

3. 解决Key冲突

虽然上面的实例可以正常的工作,但是,如果存在重复的键会发生什么情况?

我们假设,我们使用书的出版年份作为Key:

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

使用上面实例使用同样的books变量作为输入,该程序会抛出一个IllegalStateException异常。

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

为了解决Key重复的问题,我们需要使用toMap()方法的另一个重载函数,该函数可以让我们定义mergeFunction参数:

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

我们mergeFunction设为在Key发生冲突时保留已有的记录,换句话说,我们只保留第一条记录:

public Map<Integer, Book> listToMapWithDupKey(List<Book> books) {
    return books.stream().collect(Collectors.toMap(Book::getReleaseYear, Function.identity(),
      (existing, replacement) -> existing));
}
@Test
public void whenMapHasDuplicateKeyThenMergeFunctionHandlesCollision() {
    Map<Integer, Book> booksByYear = convertToMap.listToMapWithDupKey(bookList);
    assertEquals(2, booksByYear.size());
    assertEquals("0395489318", booksByYear.get(1954).getIsbn());
}

4. 返回其它Map实现

默认情况下,toMap方法返回一个HashMap的实例。

我们可以返回不同类型的Map实例吗?当然可以:

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

这个toMap()的重载函数有一个mapSupplier参数用于返回一个自定义的Map实例。

4.1 List转ConcurrentMap

我们来看一下与上面几乎一样的例子,不同之处在于,该例中返回的是ConcurrentHashMap:

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

然后是我们的测试代码:

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

4.2 有序的Map

最后,我们来看一下如何返回一个有序的Map。返回一个有序的Map只需要返回TreeMap的一个实例就行了。

TreeMap默认根据Key进行排序,因此我们可以避免显式的进行排序。

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

本例中,将根据书名的字母序进行排序。

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

5. 总结

在本例中,我们学会了使用Collectors类的toMap()方法,该方法可以让我们将Stream转为Map,此外,还学习了如何解决Key冲突以及返回Map接口的不同实现。

发表评论

电子邮件地址不会被公开。 必填项已用*标注