JavaのStream.collect()を使いこなす — toList以外も覚えよう
はじめに
Stream APIの終端操作といえば collect(Collectors.toList()) が定番ですが、Collectors にはほかにも便利なメソッドがたくさんあります。
この記事では実務でよく使う collect() のパターンをまとめます。
collect()の基本
collect() はStreamの要素をコレクションや文字列などにまとめる終端操作です。
List<String> names = List.of("Alice", "Bob", "Charlie");
// Streamに変換して処理し、collect()でまとめる
List<String> result = names.stream()
.filter(name -> name.length() > 3)
.collect(Collectors.toList());
System.out.println(result); // [Alice, Charlie]
引数に渡す Collectors.toList() などをコレクターと呼びます。コレクターを変えることで出力の形式を柔軟に変えられます。
toList() — リストにまとめる
最もよく使う基本パターンです。
List<String> names = List.of("Alice", "Bob", "Charlie", "Dave");
List<String> longNames = names.stream()
.filter(name -> name.length() > 3)
.collect(Collectors.toList());
System.out.println(longNames); // [Alice, Charlie, Dave]
Java 16以降は Stream.toList() と書けてさらに短くなります。
// Java 16以降のショートカット(変更不可リストを返す)
List<String> longNames = names.stream()
.filter(name -> name.length() > 3)
.toList();
toSet() — 重複を除いてSetにまとめる
List<Integer> numbers = List.of(1, 2, 2, 3, 3, 3);
Set<Integer> unique = numbers.stream()
.collect(Collectors.toSet());
System.out.println(unique); // [1, 2, 3](順序は保証されない)
重複を除きたい場合に使います。順序が必要なら Collectors.toCollection(TreeSet::new) で対応できます。
joining() — 文字列として結合する
List<String> names = List.of("Alice", "Bob", "Charlie");
// 単純に連結
String joined = names.stream()
.collect(Collectors.joining());
System.out.println(joined); // "AliceBobCharlie"
// 区切り文字を指定
String csv = names.stream()
.collect(Collectors.joining(", "));
System.out.println(csv); // "Alice, Bob, Charlie"
// 区切り文字 + 前後の文字を指定
String formatted = names.stream()
.collect(Collectors.joining(", ", "[", "]"));
System.out.println(formatted); // "[Alice, Bob, Charlie]"
CSV出力やログ用の文字列生成でよく使います。
toMap() — Mapに変換する
List<String> names = List.of("Alice", "Bob", "Charlie");
// 名前 → 文字数 のマップを作る
Map<String, Integer> nameLengthMap = names.stream()
.collect(Collectors.toMap(
name -> name, // キー
name -> name.length() // 値
));
System.out.println(nameLengthMap);
// {Alice=5, Bob=3, Charlie=7}
罠: キーが重複するとエラー
List<String> withDuplicate = List.of("Alice", "Alice", "Bob");
// キーが重複するとIllegalStateExceptionが発生!
Map<String, Integer> map = withDuplicate.stream()
.collect(Collectors.toMap(
name -> name,
name -> name.length()
));
重複が起きうる場合は第3引数でマージ方法を指定します。
// 重複時は後勝ち(上書き)
Map<String, Integer> map = withDuplicate.stream()
.collect(Collectors.toMap(
name -> name,
name -> name.length(),
(existing, replacement) -> replacement // 重複時の処理
));
groupingBy() — グループ分けする
groupingBy() は要素をグループ化して Map<キー, List<要素>> にまとめます。
List<String> names = List.of("Alice", "Bob", "Anna", "Brian", "Charlie");
// 先頭文字でグループ化
Map<Character, List<String>> grouped = names.stream()
.collect(Collectors.groupingBy(name -> name.charAt(0)));
System.out.println(grouped);
// {A=[Alice, Anna], B=[Bob, Brian], C=[Charlie]}
// グループごとの件数を数える
Map<Character, Long> countByInitial = names.stream()
.collect(Collectors.groupingBy(
name -> name.charAt(0),
Collectors.counting()
));
System.out.println(countByInitial);
// {A=2, B=2, C=1}
DBのGROUP BYに相当する操作をJavaで行う時に便利です。
partitioningBy() — 2つに分類する
条件でtrueとfalseの2グループに分けます。
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Map<Boolean, List<Integer>> partitioned = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
System.out.println(partitioned.get(true)); // [2, 4, 6, 8, 10](偶数)
System.out.println(partitioned.get(false)); // [1, 3, 5, 7, 9](奇数)
「有効/無効」「合格/不合格」など2択の分類に使います。
counting() — 件数を数える
List<String> names = List.of("Alice", "Bob", "Anna", "Brian", "Charlie");
// 先頭文字ごとの件数
Map<Character, Long> count = names.stream()
.collect(Collectors.groupingBy(
name -> name.charAt(0),
Collectors.counting()
));
System.out.println(count); // {A=2, B=2, C=1}
groupingBy() の第2引数として組み合わせて使うのが定番です。
まとめ
| コレクター | 用途 |
|---|---|
toList() |
リストにまとめる |
toSet() |
重複を除いてSetにまとめる |
joining() |
文字列として結合する |
toMap() |
Mapに変換する(キー重複に注意) |
groupingBy() |
条件でグループ化する |
partitioningBy() |
2グループに分類する |
counting() |
件数を数える |
おわりに
collect() は toList() だけ覚えておけば最低限使えますが、groupingBy() や joining() を知っているとforループで書いていた処理を短く書けるようになります。
「このループ、collectで書けるかも?」という視点でコードを見直してみてください。