Java Stream でよく使う Map 変換

f:id:Naotsugu:20191225000550p:plain


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'}}


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 が同じものをグルーピングできました。


グルーピングして集計

売上日別の価格集計などは 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 が奇数か偶数かにより分類する例です。



Javaによる関数型プログラミング ―Java 8ラムダ式とStream

Javaによる関数型プログラミング ―Java 8ラムダ式とStream

  • 作者:Venkat Subramaniam
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2014/10/24
  • メディア: 単行本(ソフトカバー)

Effective Java (3rd Edition)

Effective Java (3rd Edition)

  • 作者:Joshua Bloch
  • 出版社/メーカー: Addison-Wesley Professional
  • 発売日: 2018/01/06
  • メディア: ペーパーバック

Modern Java in Action: Lambdas, streams, functional and reactive programming

Modern Java in Action: Lambdas, streams, functional and reactive programming