Java 8コレクターtoMap

1. 前書き

このクイックチュートリアルでは、_Collectors_クラスの_toMap()_メソッドについて説明します。 __Stream__sを_Map_インスタンスに収集するために使用します。
*ここで説明するすべての例では、本のリストを出発点として使用し、異なる_Map_実装に変換します。*

2. List_から_Map

_List_を_Map_に変換することにより、最も単純なケースから始めます。
_Book_クラスは次のように定義されます:
class Book {
    private String name;
    private int releaseYear;
    private String isbn;

    // getters and setters
}
そして、コードを検証するための本のリストを作成します。
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_を使用すると、マップのキーと値を取得する方法の戦略を示すことができます。*

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. キーの競合を解決する

*上記の例はうまくいきましたが、キーが重複しているとどうなりますか?*
各_Book_ ’のリリース年ごとに_Map_をキー設定したと想像してみましょう。
public Map<Integer, Book> listToMapWithDupKeyError(List<Book> books) {
    return books.stream().collect(Collectors.toMap(Book::getReleaseYear, Function.identity()));
}
以前の書籍リストを考えると、_IllegalStateException_が表示されます。
@Test(expected = IllegalStateException.class)
public void whenMapHasDuplicateKey_without_merge_function_then_runtime_exception() {
    convertToMap.listToMapWithDupKeyError(bookList);
}
これを解決するには、追加のパラメーター_mergeFunction_を使用して別のメソッドを使用する必要があります。
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
  Function<? super T, ? extends U> valueMapper,
  BinaryOperator<U> mergeFunction)
衝突の場合、既存のエントリを保持することを示すマージ関数を紹介しましょう。
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. その他のマップタイプ

デフォルトでは、_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)
ここで、_mapSupplier_は、結果とともに新しい空の_Map_を返す関数です。

4.1. List_から_ConcurrentMap

上記と同じ例を取り上げて、_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));
}
コードをテストしてみましょう。
@Test
public void whenCreateConcurrentHashMap() {
    assertTrue(convertToMap.listToConcurrentMap(bookList) instanceof ConcurrentHashMap);
}

[[“sorted_map]]
==== 4.2. 並べ替えられた_Map_

最後に、ソートされたマップを返す方法を見てみましょう。 そのためには、リストを並べ替え、_TreeMap_を_mapSupplier_パラメーターとして使用する必要があります。
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));
}
上記のコードは、ブック名に基づいてリストをソートし、結果を_TreeMap:_に収集します
@Test
public void whenMapisSorted() {
    assertTrue(convertToMap.listToSortedMap(bookList).firstKey().equals("The Fellowship of the Ring"));
}

5. 結論

この記事では、_Collectors_クラスの__toMap()__methodを調べました。 _Stream_から新しい_Map_を作成できます。 また、キーの競合を解決し、異なるマップ実装を作成する方法も学びました。
いつものように、コードはhttps://github.com/eugenp/tutorials/tree/master/java-collections-conversions[GitHub上]で入手できます。