Stream.collect(Collectors.toList()/toSet()/toMap()) — ストリーム結果変換
Stream の終端操作である collect は、加工した要素を List・Set・Map にまとめるための定番メソッドです。filter・map・sorted などの中間操作のあと、最終形に「変換」するのが collect の役割です。
基本の考え方
- 役割: 加工済みのストリーム要素をコレクションへ「集約」する終端操作。ストリームは一度流したら再利用しないので、結果は collect で取り出す。
- 主な変換先:
Collectors.toList()、Collectors.toSet()、Collectors.toMap(keyMapper, valueMapper, ...)が軸。目的に合わせて使い分ける。
toList と toSet(順序のまま/重複排除)
import java.util.*;
import java.util.stream.*;
List<String> names = List.of("Tanaka", "Sato", "Sato");
// List へ(元順序を保ちやすい)
List<String> list = names.stream()
.filter(s -> s.length() >= 4)
.collect(Collectors.toList());
// Set へ(重複排除)
Set<String> set = names.stream()
.collect(Collectors.toSet());
Java- ポイント:
- toList: フィルタや変換後の結果を順序付きで受け取りたいときの基本形。
- toSet: 要素の重複をなくしたいときに使う(順序は実装依存)。
toMap(キー/値を指定して Map 化)
import java.util.*;
import java.util.stream.*;
record User(String name, int age) {}
List<User> users = List.of(
new User("Tanaka", 30), new User("Sato", 25), new User("Kato", 25)
);
// 基本:キーと値を指定
Map<String, Integer> byNameAge = users.stream()
.collect(Collectors.toMap(User::name, User::age));
// 重複キーが起きる場合はマージ関数を指定
Map<Integer, String> namesByAge = users.stream()
.collect(Collectors.toMap(
User::age,
User::name,
(a, b) -> a + "," + b // 同じキー(age)にぶつかったら結合
));
Java- ポイント:
- キー重複対策: toMap はキーが重複すると例外。第三引数のマージ関数で解決方法を必ず指定する。
- LinkedHashMap にしたい: 4番目の引数で
LinkedHashMap::newを渡すと挿入順が保たれる。ランキングや順序付きの結果に便利。
Map<String, Integer> ordered = users.stream()
.sorted(Comparator.comparingInt(User::age).reversed())
.collect(Collectors.toMap(
User::name,
User::age,
(a,b) -> a,
LinkedHashMap::new
));
Java例題で身につける
例題1: 単語のフィルタ+整形を List に集約
List<String> words = List.of("apple", "banana", "apricot", "pear");
List<String> startWithA = words.stream()
.filter(w -> w.startsWith("a"))
.map(String::toUpperCase)
.collect(Collectors.toList());
Java- ねらい: 中間操作→終端操作の流れを体感(filter→map→collect)。
例題2: 重複排除して Set に集約
List<Integer> nums = List.of(1, 2, 2, 3, 3, 3);
Set<Integer> unique = nums.stream()
.collect(Collectors.toSet()); // [1,2,3](順序は実装依存)
Java- ねらい: 集約で重複をなくす(集合的な扱い)。
例題3: 配列の toMap(ID→オブジェクト)
record Item(String id, String name) {}
List<Item> items = List.of(
new Item("A001", "Apple"),
new Item("B002", "Banana"),
new Item("C003", "Cherry")
);
Map<String, Item> byId = items.stream()
.collect(Collectors.toMap(Item::id, i -> i));
Java- ねらい: 参照戻しに便利な「キー→値」辞書の作成。
よくある落とし穴と回避策
- 落とし穴: toMap でキー重複が発生して例外。
- 回避: 第三引数で「重複時の結合ルール」を指定する(加算、先勝、後勝、連結など)。
- 落とし穴: 結果の順序を期待しているのに Map が並ばない。
- 回避: 4番目の引数に
LinkedHashMap::newを渡して挿入順を保持する。ソート後に collect するとその順序が保てる。
- 回避: 4番目の引数に
- 落とし穴: null をキーや値にしたい。
- 回避: toMap の keyMapper/valueMapper は null 非推奨。null を生成しないマッピングを設計する。
- 落とし穴: 巨大データで toSet の順序が必要。
- 回避: 順序維持が必要なら
toCollection(LinkedHashSet::new)を使う。
- 回避: 順序維持が必要なら
ミニテンプレート集
- 基本の List 化
List<T> result = stream.collect(Collectors.toList());
Java- 重複排除の Set 化
Set<T> result = stream.collect(Collectors.toSet());
Java- 重複キー対策付き toMap
Map<K,V> m = stream.collect(Collectors.toMap(
x -> keyOf(x),
x -> valueOf(x),
(a,b) -> merge(a,b)
));
Java- 順序保持の LinkedHashMap で収集
Map<K,V> m = stream.collect(Collectors.toMap(
kf, vf, (a,b) -> a, LinkedHashMap::new
));
Java- コレクション種別を指定して収集
LinkedHashSet<T> s = stream.collect(Collectors.toCollection(LinkedHashSet::new));
Java補足(collect の位置づけ)
- 中間 vs 終端: filter・map などの「中間操作」で形を整え、collect の「終端操作」で結果を取り出すという流れが基本。宣言的に処理を組み立てられるのが Stream の利点。
- 使い捨てモデル: ストリームは一度流したら再利用しないため、繰り返し使うデータは collect したコレクションを保持する。
まとめ
- collect はストリーム結果をコレクションに変換する終端操作。とくに
toList・toSet・toMapは基本中の基本。 - toMap では重複キー対策と順序保持(LinkedHashMap 指定)が実務で重要。filter・map などと組み合わせて、読みやすく安全なデータ処理パイプラインを作れる。
