Java | Java 詳細・モダン文法:Stream API 深掘り – 中間操作と終端操作

Java Java
スポンサーリンク

中間操作と終端操作を一言でいうと

Stream API の「中間操作」と「終端操作」は、

“流れをつくる操作(中間)” と “流れを終わらせて結果を取り出す操作(終端)”

という役割分担になっています。

この違いを理解すると、Stream の動きが一気にクリアになります。
特に重要なのは、

  • 中間操作は「Stream を返す」のでつなげられる
  • 終端操作は「Stream を返さない」のでそこで終わる
  • 中間操作は“遅延評価”で、終端操作が呼ばれるまで実行されない

という 3 点です。

ここを丁寧に噛み砕いていきます。


中間操作とは何か

Stream を別の Stream に変換する“途中のステップ”

中間操作(intermediate operation)は、

「ストリームを受け取り、別のストリームを返す操作」

です。

代表的な中間操作は次のようなものです。

  • filter(条件に合うものだけ残す)
  • map(別の型に変換する)
  • sorted(並び替える)
  • distinct(重複を取り除く)
  • limit / skip(一部だけ取る・飛ばす)
  • peek(途中で値を覗く)

中間操作の特徴は、

「Stream を返すので、何個でもつなげられる」

という点です。

names.stream()
     .filter(s -> s.length() >= 4)
     .map(String::toUpperCase)
     .sorted();
Java

ここまでではまだ“流れを宣言しただけ”で、実行はされていません。

中間操作は“遅延評価”である

中間操作は、呼んだ瞬間には実行されません。

names.stream()
     .filter(s -> {
         System.out.println("filter: " + s);
         return s.length() >= 4;
     })
     .map(s -> {
         System.out.println("map: " + s);
         return s.toUpperCase();
     });
// ここでは何も出力されない
Java

実際の処理は、終端操作が呼ばれた瞬間に初めて動きます。

この「遅延評価」が Stream の効率性の源です。


終端操作とは何か

Stream の流れを“終わらせて”結果を取り出す操作

終端操作(terminal operation)は、

「ストリームを消費して、結果を返す操作」

です。

代表的な終端操作は次のようなものです。

  • collect(リストなどに集める)
  • forEach(1 件ずつ処理する)
  • count(件数を数える)
  • findFirst / findAny(1 件取り出す)
  • anyMatch / allMatch / noneMatch(条件判定)
  • reduce(畳み込み)

終端操作の特徴は、

「Stream を返さない」

という点です。

long count = names.stream()
                  .filter(s -> s.length() >= 4)
                  .count();  // ここでパイプラインが終了
Java

終端操作を呼んだ瞬間に、
それまで積み重ねてきた中間操作が“まとめて実行”されます。


中間操作と終端操作の流れを例で理解する

例:名前リストから「4 文字以上の名前の長さ」を集める

import java.util.List;
import java.util.stream.Collectors;

public class Example {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Charlie", "Ann");

        List<Integer> result =
                names.stream()                          // Stream<String>
                     .filter(s -> s.length() >= 4)      // Stream<String>
                     .map(s -> s.length())              // Stream<Integer>
                     .collect(Collectors.toList());     // List<Integer>

        System.out.println(result); // [5, 7]
    }
}
Java

このパイプラインを“処理の流れ”として読むとこうなります。

  1. ソース:names.stream()
    → 「名前の流れを作る」
  2. 中間:filter
    → 「4 文字以上だけ残す」
  3. 中間:map
    → 「名前 → 長さ に変換する」
  4. 終端:collect
    → 「結果をリストに集める」

このように、Stream は「処理のステップ」をそのままコードに落とし込めます。


中間操作の実行タイミングを深掘りする

終端操作が呼ばれるまで“実行されない”理由

Stream は「1 要素ずつパイプラインを通す」仕組みになっています。

例えば:

names.stream()
     .filter(...)
     .map(...)
     .count();
Java

このときの実行順序は、

  • 1 件目を filtermapcount に流す
  • 2 件目を filtermapcount に流す
  • 3 件目を filtermapcount に流す

という形で、「横方向に」処理されます。

これにより、

  • 無駄な処理をしない
  • 途中で条件が満たされたらすぐ終わる(findFirst など)
  • 大量データでも効率的

というメリットが生まれます。


中間操作と終端操作の違いを“型”で見抜く

中間操作は「Stream を返す」

終端操作は「Stream を返さない」

これが最も明確な違いです。

中間操作の例:

Stream<T> filter(Predicate<? super T> predicate)
Stream<R> map(Function<? super T, ? extends R> mapper)
Stream<T> sorted()
Java

終端操作の例:

long count()
void forEach(Consumer<? super T> action)
Optional<T> findFirst()
<R> R collect(Collector<? super T, A, R> collector)
Java

Stream を返すかどうかを見るだけで、
そのメソッドが中間操作か終端操作かが一瞬で分かります。


よくあるつまずきポイントと対処法

「中間操作だけ書いても何も起きない」

これは Stream の“遅延評価”が原因です。

names.stream().filter(s -> s.length() >= 4); // 何も起きない
Java

必ず終端操作を付けてください。

names.stream()
     .filter(s -> s.length() >= 4)
     .forEach(System.out::println);
Java

「途中で値を見たい」

peek を使います。

names.stream()
     .filter(s -> s.length() >= 4)
     .peek(s -> System.out.println("after filter: " + s))
     .map(String::toUpperCase)
     .peek(s -> System.out.println("after map: " + s))
     .forEach(System.out::println);
Java

「途中でリストにしたい」

一度 collect で終端させてから、新しいパイプラインを始めます。

List<String> filtered =
        names.stream()
             .filter(s -> s.length() >= 4)
             .toList();

filtered.stream()
        .map(String::toUpperCase)
        .forEach(System.out::println);
Java

まとめ:中間操作と終端操作を自分の言葉で整理する

中間操作と終端操作を一文でまとめるなら、

中間操作は「流れを変えるステップ」、終端操作は「流れを終わらせて結果を取り出すステップ」

です。

特に押さえておきたいのは、

  • 中間操作は Stream を返すのでつなげられる
  • 終端操作は Stream を返さないのでそこで終わる
  • 中間操作は遅延評価で、終端操作が呼ばれるまで実行されない
  • パイプラインは「左から右へ、型と意味の変化を追う」と読みやすい

という点です。

タイトルとURLをコピーしました