Java 逆引き集 | PartitioningBy / GroupingBy(Collectors) — 集計・グループ化

Java Java
スポンサーリンク

PartitioningBy / GroupingBy(Collectors) — 集計・グループ化

Stream の終端操作 collect における定番が partitioningBy(2分割)と groupingBy(多分割)。「条件で振り分ける」「キーで束ねる」を1行で書け、さらに件数や合計などの集計も同時に行えます。初心者でも迷わない使い分けと、よく使う下流コレクタ(downstream)を例付きで整理します。


基本の考え方

  • partitioningBy: 真偽(Predicate)で「true/false」2グループに分ける。結果は Map<Boolean, List<T>>。
  • groupingBy: キー(Function)で複数グループに分ける。結果は Map<K, List<T>>。
  • downstream(下流): デフォルトでは「各グループごとに List<T>」だが、countingsummingInt などを渡すと「件数」「合計」「平均」「別型」へ直接集約できる。

partitioningBy(条件で2分割)

基本(偶数・奇数で分ける)

import java.util.*;
import java.util.stream.*;

List<Integer> nums = List.of(1, 2, 3, 4, 5, 6);

Map<Boolean, List<Integer>> evenOdd = nums.stream()
    .collect(Collectors.partitioningBy(n -> n % 2 == 0));

System.out.println(evenOdd.get(true));  // 偶数: [2, 4, 6]
System.out.println(evenOdd.get(false)); // 奇数: [1, 3, 5]
Java

下流で件数・平均などを直接出す

// 各グループの要素数
Map<Boolean, Long> countByParity = nums.stream()
    .collect(Collectors.partitioningBy(n -> n % 2 == 0, Collectors.counting()));

// 各グループの平均(double)
Map<Boolean, Double> avgByParity = nums.stream()
    .collect(Collectors.partitioningBy(n -> n % 2 == 0,
        Collectors.averagingInt(n -> n)));
Java
  • コツ: partitioningBy は常にキーが true/false の2つ。グループが空でもキーは存在します。

groupingBy(キーで多分割)

基本(長さで分ける)

import java.util.*;
import java.util.stream.*;

List<String> words = List.of("apple", "banana", "pear", "plum", "kiwi");

Map<Integer, List<String>> byLen = words.stream()
    .collect(Collectors.groupingBy(String::length));

System.out.println(byLen.get(5)); // ["apple"]
System.out.println(byLen.get(6)); // ["banana"]
Java

下流を使って「件数」「合計」「最大」へ

record User(String name, int age) {}
List<User> users = List.of(
    new User("Tanaka", 30), new User("Sato", 25), new User("Kato", 25), new User("Ito", 40)
);

// 年齢でグループ化 → 件数
Map<Integer, Long> countByAge = users.stream()
    .collect(Collectors.groupingBy(User::age, Collectors.counting()));

// 年齢でグループ化 → 名前のリスト(mapping)
Map<Integer, List<String>> namesByAge = users.stream()
    .collect(Collectors.groupingBy(User::age,
        Collectors.mapping(User::name, Collectors.toList())));

// 名前の頭文字でグループ化 → 最大年齢(maxBy)
Map<Character, Integer> maxAgeByInitial = users.stream()
    .collect(Collectors.groupingBy(u -> u.name().charAt(0),
        Collectors.collectingAndThen(
            Collectors.maxBy(Comparator.comparingInt(User::age)),
            opt -> opt.map(User::age).orElse(0)
        )));
Java
  • コツ: mapping は「値を別型に変換してから集約」する定番。collectingAndThen は「最終加工」を挟めます。

下流コレクタの便利レシピ

よく使うもの

  • counting: 件数
  • summingInt/Long/Double: 合計
  • averagingInt/Long/Double: 平均
  • maxBy/minBy: 最大・最小(Optional 返し)
  • mapping: 値を取り出して別の Collector へ
  • joining: 文字列結合(区切り/前後/間も指定可)
  • collectingAndThen: 最終加工(例: Optional の中身を抽出)

例: 部門別の合計給与とメンバー名一覧

record Emp(String dept, String name, int salary) {}
List<Emp> emps = List.of(
    new Emp("Sales", "Tanaka", 300),
    new Emp("Sales", "Sato",   250),
    new Emp("Dev",   "Ito",    500)
);

// 合計給与
Map<String, Integer> sumSalary = emps.stream()
    .collect(Collectors.groupingBy(Emp::dept, Collectors.summingInt(Emp::salary)));

// メンバー名一覧
Map<String, List<String>> namesByDept = emps.stream()
    .collect(Collectors.groupingBy(Emp::dept,
        Collectors.mapping(Emp::name, Collectors.toList())));
Java

例題で身につける

例題1: 合格点で partitioning(合格/不合格)+人数

List<Integer> scores = List.of(72, 88, 90, 65, 99, 80);

Map<Boolean, Long> passFailCount = scores.stream()
    .collect(Collectors.partitioningBy(s -> s >= 80, Collectors.counting()));

System.out.println(passFailCount); // {false=2, true=4}
Java

例題2: 年齢で grouping(件数と平均を同時に)

record User(String name, int age) {}
List<User> users = List.of(
    new User("Tanaka", 30), new User("Sato", 25), new User("Kato", 25), new User("Ito", 40)
);

// 件数
Map<Integer, Long> countByAge = users.stream()
    .collect(Collectors.groupingBy(User::age, Collectors.counting()));

// 平均(年齢キーの平均はその年齢と同じになるため、部署など別キーでの平均が現実向き)
Map<Character, Double> avgAgeByInitial = users.stream()
    .collect(Collectors.groupingBy(u -> u.name().charAt(0),
        Collectors.averagingInt(User::age)));
Java

例題3: 複合キーで grouping(多段ネスト)

record Tx(String user, String category, int amount) {}
List<Tx> txs = List.of(
    new Tx("Tanaka", "Food", 1200),
    new Tx("Tanaka", "Book",  800),
    new Tx("Sato",   "Food", 1500),
    new Tx("Sato",   "Food",  700)
);

Map<String, Map<String, Integer>> sumByUserCategory = txs.stream()
    .collect(Collectors.groupingBy(Tx::user,
        Collectors.groupingBy(Tx::category, Collectors.summingInt(Tx::amount))));

System.out.println(sumByUserCategory);
// {Tanaka={Food=1200, Book=800}, Sato={Food=2200}}
Java

テンプレート集と実務のコツ

  • 2分割(件数/合計/平均)
Map<Boolean, Long> counts = stream.collect(
    Collectors.partitioningBy(pred, Collectors.counting()));
Map<Boolean, Integer> sums = stream.collect(
    Collectors.partitioningBy(pred, Collectors.summingInt(toInt)));
Java
  • 多分割(件数/合計/最大)
Map<K, Long> counts = stream.collect(
    Collectors.groupingBy(key, Collectors.counting()));
Map<K, Integer> sums = stream.collect(
    Collectors.groupingBy(key, Collectors.summingInt(toInt)));
Map<K, T> maxs = stream.collect(
    Collectors.groupingBy(key,
        Collectors.collectingAndThen(
            Collectors.maxBy(Comparator.comparingInt(toIntProp)),
            Optional::get)));
Java
  • 値変換してから集約(mapping)
Map<K, List<U>> result = stream.collect(
    Collectors.groupingBy(key, Collectors.mapping(mapper, Collectors.toList())));
Java
  • 順序保持(LinkedHashMap)や EnumMap 指定
Map<K, V> ordered = stream.collect(
    Collectors.groupingBy(key, LinkedHashMap::new, downstream));

Map<MyEnum, V> enumMap = stream.collect(
    Collectors.groupingBy(e -> e.type(), () -> new EnumMap<>(MyEnum.class), downstream));
Java
  • 落とし穴と回避
    • 巨大グループの List が重い: counting/summing/maxBy で直接集約してメモリ節約。
    • Optional の扱いが煩雑: collectingAndThen(..., Optional::orElse(default)) で最終加工。
    • キーが null: groupingBy のキー関数は null を返さない設計にする(NullPointer 問題の予防)。
    • 読みやすさ: ネストが深くなったら変数に受ける、関数を事前定義して可読性を保つ。

まとめ

  • partitioningBy は「条件で2分割」、groupingBy は「キーで多分割」。
  • 下流コレクタを組み合わせると、「各グループの件数・合計・平均・最大」などが一発で得られる。
  • メモリと可読性を意識して、必要な最終形(件数・合計・リスト・Map)に直接集約するのが実務のコツ。
タイトルとURLをコピーしました