1. 導入:なぜこの違いを理解すべきか
Java 8で導入されたStream APIは、コレクション操作を関数型プログラミングスタイルで記述できる強力な武器です。しかし、実務現場では「Streamが動かない」「思ったような結果にならない」というトラブルが頻発します。その原因の多くは、Streamの操作が「中間操作」と「終端操作」に分かれていることを意識していないことにあります。この二つの特性を理解すれば、コードのパフォーマンスを最適化し、バグを未然に防ぐことができます。
2. 基礎知識:Streamの仕組み
Stream APIは「パイプライン」構造を持っています。データを流し込み、加工(中間操作)し、最終的に結果を取り出す(終端操作)という流れです。
中間操作 (Intermediate Operations)
データに対してフィルタリングや変換を行う操作です。最大の特徴は「怠惰評価(Lazy Evaluation)」です。終端操作が呼び出されるまで、中間操作は一切実行されません。これにより、不要な計算を省略し、メモリ消費を抑えることができます。
終端操作 (Terminal Operations)
Streamパイプラインを起動し、結果を生成する操作です。これを行うとStreamは「消費」され、再利用できなくなります。collect(), forEach(), reduce()などが該当します。
3. 実装/解決策:パイプラインの適切な構築
中間操作を連ねる際は、計算量を意識した順序が重要です。例えば、filter(絞り込み)はできるだけ前方に配置し、処理対象のデータ量を減らすのが基本です。
4. サンプルプログラム
以下のコードは、リスト内の偶数を抽出し、文字列に変換してリスト化する一連の流れです。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamExample {
public static void main(String[] args) {
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// パイプラインの構築
List result = numbers.stream()
// 中間操作: 偶数のみに絞り込む(この時点では実行されない)
.filter(n -> n % 2 == 0)
// 中間操作: 文字列に変換(この時点でも実行されない)
.map(n -> "番号:" + n)
// 終端操作: ここで初めてストリームが起動し、上の操作が実行される
.collect(Collectors.toList());
System.out.println(result); // [番号:2, 番号:4, 番号:6]
}
}
5. 応用・注意点:現場で陥りやすい罠
実務で特に注意すべきポイントを3点挙げます。
① 終端操作を忘れない
中間操作だけを記述しても、Streamは何も処理しません。「実行されない」というバグの原因になります。
② ストリームの再利用は不可
一度終端操作を行ったStreamを再度使用しようとすると、IllegalStateExceptionが発生します。
例:
Stream
s.collect(Collectors.toList());
s.count(); // ここで例外発生
③ Sequenced Collections(Java 21以降)との併用
Java 21から導入されたSequencedCollectionは、リストの先頭や末尾へのアクセスが容易になりました。Streamと組み合わせる際は、あらかじめSequencedCollectionで要素を絞り込んでからStreamに変換すると、より可読性の高いコードになります。
結論として、中間操作は「計画」、終端操作は「実行」と覚えると、Streamの挙動を直感的に把握できるようになります。この境界線を意識して、堅牢でクリーンなJavaコードを書いていきましょう。

コメント