collect(Collectors.groupingBy(…)) — グルーピング集計
Stream API の collect(Collectors.groupingBy(...)) は「キーごとに要素をまとめる」ための強力な集約方法です。SQL の GROUP BY に近いイメージで、カテゴリ別集計や分類に役立ちます。
基本の仕組み
- groupingBy(keyExtractor):
- 要素から「キー」を取り出し、そのキーごとにリストへまとめる。
- 戻り値は
Map<K, List<T>>。
- groupingBy(keyExtractor, downstreamCollector):
- キーごとに「さらに集約」を行う。
- 例: 件数を数える、合計を出す、セットにするなど。
基本コード例
1) 単純なグルーピング
import java.util.*;
import java.util.stream.*;
public class GroupingBasic {
public static void main(String[] args) {
List<String> words = List.of("apple","banana","apricot","blueberry","cherry");
Map<Character, List<String>> grouped =
words.stream()
.collect(Collectors.groupingBy(w -> w.charAt(0)));
System.out.println(grouped);
// {a=[apple, apricot], b=[banana, blueberry], c=[cherry]}
}
}
Java- ポイント: 先頭文字をキーにして分類。
2) 件数を数える(downstream に counting)
List<String> words = List.of("apple","banana","apricot","blueberry","cherry");
Map<Character, Long> counts =
words.stream()
.collect(Collectors.groupingBy(w -> w.charAt(0),
Collectors.counting()));
System.out.println(counts);
// {a=2, b=2, c=1}
Java- ポイント:
Collectors.counting()を使うとキーごとの件数が得られる。
3) 合計値を出す(summingInt)
record Product(String category, int price) {}
List<Product> products = List.of(
new Product("fruit",100),
new Product("fruit",200),
new Product("veg",150),
new Product("veg",120)
);
Map<String, Integer> sumByCategory =
products.stream()
.collect(Collectors.groupingBy(Product::category,
Collectors.summingInt(Product::price)));
System.out.println(sumByCategory);
// {fruit=300, veg=270}
Java- ポイント:
summingIntで数値フィールドを合計。
4) セットにまとめる(toSet)
List<String> words = List.of("apple","banana","apricot","blueberry","cherry");
Map<Character, Set<String>> grouped =
words.stream()
.collect(Collectors.groupingBy(w -> w.charAt(0),
Collectors.toSet()));
System.out.println(grouped);
// {a=[apple, apricot], b=[banana, blueberry], c=[cherry]}
Java- ポイント: リストではなく Set にすれば重複排除。
例題で理解する
例題1: 学生を「合否」で分類
record Student(String name, int score) {}
List<Student> students = List.of(
new Student("Tanaka",80),
new Student("Sato",55),
new Student("Ito",90)
);
Map<String, List<Student>> grouped =
students.stream()
.collect(Collectors.groupingBy(s -> s.score >= 60 ? "Pass" : "Fail"));
System.out.println(grouped);
// {Pass=[Tanaka, Ito], Fail=[Sato]}
Java例題2: 社員を部署ごとに人数集計
record Employee(String dept, String name) {}
List<Employee> emps = List.of(
new Employee("Sales","A"),
new Employee("Sales","B"),
new Employee("IT","C"),
new Employee("IT","D"),
new Employee("HR","E")
);
Map<String, Long> deptCounts =
emps.stream()
.collect(Collectors.groupingBy(Employee::dept, Collectors.counting()));
System.out.println(deptCounts);
// {Sales=2, IT=2, HR=1}
Java例題3: 商品をカテゴリ別に平均価格
Map<String, Double> avgByCategory =
products.stream()
.collect(Collectors.groupingBy(Product::category,
Collectors.averagingInt(Product::price)));
System.out.println(avgByCategory);
// {fruit=150.0, veg=135.0}
Javaテンプレート集
- 基本分類
Map<K, List<T>> map = stream.collect(Collectors.groupingBy(x -> key));
Java- 件数集計
Map<K, Long> map = stream.collect(Collectors.groupingBy(x -> key, Collectors.counting()));
Java- 合計集計
Map<K, Integer> map = stream.collect(Collectors.groupingBy(x -> key, Collectors.summingInt(x -> value)));
Java- 平均集計
Map<K, Double> map = stream.collect(Collectors.groupingBy(x -> key, Collectors.averagingInt(x -> value)));
Java- セット化
Map<K, Set<T>> map = stream.collect(Collectors.groupingBy(x -> key, Collectors.toSet()));
Java落とし穴と回避策
- キーが null: groupingBy は null キーも許すが Map 実装に依存。避けるか事前に処理。
- 大量データ: distinct や groupingBy は全件保持するためメモリ負荷が大きい。必要なら DB 側で集計。
- equals/hashCode: キーの判定は equals/hashCode に依存。独自型は正しく実装する。
- 結果の Map 型: デフォルトは HashMap。順序が必要なら
groupingBy(..., LinkedHashMap::new, downstream)を使う。
まとめ
collect(Collectors.groupingBy(...))は「キーごとに分類・集計」する便利な仕組み。- downstream を組み合わせると件数・合計・平均・セット化など多彩な集約が可能。
- SQL の GROUP BY と同じ感覚で使えるので、データ処理やレポート生成に役立つ。
👉 練習課題: 「社員リストを部署ごとに分類し、部署ごとの平均年齢を求める」コードを書いてみると、groupingBy の力が実感できます。
