- Map へ変換(キー重複無し)
- Map へ変換(キー重複有り)
- 値が null 値となる場合に注意
- LinkedHashMap へ変換
- 任意キーでグルーピング
- 任意キーでグルーピングしてマッピング
- グルーピングして集計
Map へ変換(キー重複無し)
以下の Item のリストを id をキーにした Map に変換します。
List<Item> list = Arrays.asList(
new Item(9, "apple"),
new Item(3, "lemon"),
new Item(6, "peach"));
collect() に Collectors.toMap を指定した終端処理で Map に変換することができます。
list.stream().collect(
Collectors.toMap(Item::getId, e -> e));
// {3 = Item{3, 'lemon'}, 6 = Item{6, 'peach'}, 9 = Item{9, 'apple'}}
上記は UnaryOperator.identity() を使って以下のように書いても同じです。
list.stream().collect(
Collectors.toMap(Item::getId, UnaryOperator.identity()));
UnaryOperator は単項演算用の関数インターフェースで、identity() は引数をそのまま返す定義となっています。
public interface UnaryOperator<T> extends Function<T, T> { static <T> UnaryOperator<T> identity() { return t -> t; } }
Item の中身を展開する場合は以下のようにすることができます。
list.stream().collect(
Collectors.toMap(Item::getId, Item::getName)));
// {3 = lemon, 6 = peach, 9 = apple}
Map へ変換(キー重複有り)
キーが重複する場合は注意が必要です。
以下の例では、id の 6 が重複します。
List<Item> list = Arrays.asList(
new Item(9, "apple"),
new Item(3, "lemon"),
new Item(6, "peach"),
new Item(6, "banana")); // duplicate
toMap() の第三引数を指定しない場合は、キー重複時に java.lang.IllegalStateException: Duplicate key Item{6, 'peach'} の例外がスローされます。
list.stream().collect(
Collectors.toMap(Item::getId, e -> e));
// java.lang.IllegalStateException: Duplicate key Item{6, 'peach'}
キー重複する場合は、重複時にどちらを採用するかを、toMap() の第三引数で指定する必要があります。
以下は先勝ちとする例です。
list.stream().collect(
Collectors.toMap(Item::getId, e -> e, (e1, e2) -> e1));
// {3=Item{3, 'lemon'}, 6=Item{6, 'peach'}, 9=Item{9, 'apple'}}
値が null 値となる場合に注意
Collectors.toMap() による Map 変換は、値が null となる場合に NullPointerException となるため注意が必要です。
List<Item> list = Arrays.asList(
new Item(9, "apple"),
new Item(3, null),
new Item(6, "peach"));
list.stream().collect(
Collectors.toMap(Item::getId, Item::getName)));
// java.lang.NullPointerException
多くの予想に反して、キー が null の場合は NullPointerException とはならず、値が null の場合に NullPointerException となります。
この問題は Java8 時代から、JDK のバグトラッカーhttps://bugs.openjdk.java.net/browse/JDK-8148463 でオープン状態のままになっています。
値が null となる可能性がある場合は、Collectors.toMap() は使わず、以下のようにすれば回避できます。
list.stream().collect(
HashMap::new, (Map m, Item i) -> m.put(i.getId(), i.getValue()), Map::putAll)
// {3 = null, 6 = peach, 9 = apple}
LinkedHashMap へ変換
並びを維持したい場合は、第四引数の mapSupplier に LinkedHashMap::new を指定します。
list.stream().collect(
Collectors.toMap(Item::getId, e -> e, (e1, e2) -> e1, LinkedHashMap::new)));
// {9 = Item{9, 'apple'}, 3 = Item{3, 'lemon'}, 6 = Item{6, 'peach'}}
任意キーでグルーピング
特定の条件でグルーピングする場合は Collectors.groupingBy を使います。
Map<Integer, List<Item>> m = list.stream().collect(
Collectors.groupingBy(Item::getId));
// {3 = [Item{3, 'lemon'}],
// 6 = [Item{6, 'peach'}, Item{6, 'banana'}],
// 9 = [Item{9, 'apple'}]}
id が同じものをグルーピングできました。
任意キーでグルーピングしてマッピング
グルーピングする値をマッピングした値にすることもできます。
Map<Integer, List<String>> m = list.stream().collect(
Collectors.groupingBy(
Item::getId,
Collectors.mapping(Item::getName, Collectors.toList())));
Collectors.groupingBy の第2引数にマッピング関数を渡します。
グルーピングして集計
売上日別の価格集計などは Collectors.reducing で畳み込みを行います。
Map<LocalDate, BigDecimal> m = list.stream().collect(
Collectors.groupingBy(
Item::getSalesDate,
Collectors.reducing(BigDecimal.ZERO, Item::getPrice, BigDecimal::add)));
groupingBy() の第二引数に Collectors.reducing を指定して集計しています。
グループ分け
Collectors.partitioningBy はある条件で2つのグループに分ける場合に利用します。
Map<Boolean, List<Item>> m = list.stream().collect(
Collectors.partitioningBy(item -> item.getId() % 2 == 0));
// {false=[Item{id=9, name='apple'}, Item{id=3, name='lemon'}],
// true=[Item{id=6, name='peach'}, Item{id=6, name='banana'}]}
id が奇数か偶数かにより分類する例です。


