なぜ今、Collectorsの使い分けが重要なのか
JavaのStream APIを扱う際、処理結果を最終的にどのような形式のコレクションとして受け取るかは、パフォーマンスやコードの安全性に直結します。特に、Java 10以降で導入された「不変(Unmodifiable)コレクション」への収集は、予期せぬデータの書き換えを防ぐために現代のJava開発では必須の知識です。本稿では、目的別に最適な収集方法を整理します。
基礎知識:Streamと収集(Reduction)
Stream APIは「データの中間処理」を行うためのものであり、そのままではリストやマップになりません。これらを最終的にコンテナに格納する操作を「収集(Reduction)」と呼び、それを担うのがCollectorsクラスです。収集先の型を意識することで、メモリ効率やスレッドセーフなコード設計が可能になります。
実装と解決策
目的に応じて以下のメソッドを使い分けます。
1. 可変コレクションへの収集
- Collectors.toList(): Listに変換。順序保持。
- Collectors.toSet(): Setに変換。重複排除。
- Collectors.toMap(): Mapに変換。キーの重複に注意。
- Collectors.toCollection(): TreeSetなど、特定の実装を指定したい場合に使用。
2. 不変コレクションへの収集(推奨)
- Collectors.toUnmodifiableList(): 変更不可能なリストを作成。
- Collectors.toUnmodifiableSet(): 変更不可能なセットを作成。
- Collectors.toUnmodifiableMap(): 変更不可能なマップを作成。
サンプルプログラム
以下のコードは、数値リストから偶数のみを抽出し、様々な形式で収集する例です。
import java.util.;
import java.util.stream.Collectors;
public class CollectorExample {
public static void main(String[] args) {
List numbers = Arrays.asList(1, 2, 2, 3, 4, 5);
// 1. 不変リストへの収集 (推奨)
List unmodifiableList = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toUnmodifiableList());
System.out.println("不変リスト: " + unmodifiableList);
// 2. Mapへの収集 (キー:数値, 値:文字列表記)
// キーが重複する場合、(existing, replacement) -> existing で既存値を優先
Map map = numbers.stream()
.distinct()
.collect(Collectors.toUnmodifiableMap(
n -> n,
n -> "Value-" + n
));
System.out.println("不変マップ: " + map);
// 3. 特定のコレクションへ収集 (TreeSetでソート済み重複なし)
TreeSet treeSet = numbers.stream()
.collect(Collectors.toCollection(TreeSet::new));
System.out.println("TreeSet: " + treeSet);
}
}
応用・注意点
現場で最も陥りやすいのは「toMap()での重複キー例外」です。キーが重複する可能性がある場合、toMap(keyMapper, valueMapper, mergeFunction)の第3引数にマージ関数(例: (old, new) -> old)を指定しないと、実行時にIllegalStateExceptionが発生します。
また、不変コレクション(Unmodifiable)は、要素にnullを含むことが禁止されています。Streamの中にnullが含まれる可能性がある場合は、事前に.filter(Objects::nonNull)を通すなどの対策を行いましょう。コードの安全性を高めるため、まずは「不変コレクション」で作成できないかを検討し、必要に応じて可変コレクションを選択する「不変ファースト」な設計を心がけてください。

コメント