模試で「Stream.iterateの出力結果を選べ」という問題に出会って、自信を持って答えられなかった経験はありませんか。

Stream APIの中でも、iterateメソッドは「無限ストリームを作る」という特殊な動きをするため、初めて見ると戸惑う人が多い機能です。さらにJava 9から引数が3つあるオーバーロードが追加されたことで、「2引数版と3引数版で何が違うのか」が混同されやすく、Java Gold 1Z0-826でもよく狙われるポイントになっています。

この記事では、Stream.iterateの基本的な仕組みから、2引数版・3引数版の違い、試験で問われやすい落とし穴まで、サンプルコードを交えて解説します。読み終えれば、Stream.iterateが出題されても迷わず答えられるようになるはずです。


Stream.iterateとは何か

Stream.iterateは、ある初期値に処理を繰り返し適用しながら、要素を生成していくStreamを作る静的メソッドです。

シンプルに言うと「前の値から次の値を計算する」という関係を使って、要素を順番に作り出していく仕組みです。

基本の書き方は次のとおりです。

Stream<T> iterate(T seed, UnaryOperator<T> f)
  • seed:最初の要素(出発点)
  • f:前の要素から次の要素を作る処理(UnaryOperator)

UnaryOperatorという用語が出てきましたが、これは「同じ型の値を受け取って、同じ型の値を返す関数」のことです。Integer型を受け取ってInteger型を返す処理であれば、UnaryOperator<Integer>になります。型が一致しない処理を渡すとコンパイルエラーになる点も、選択肢問題で狙われやすいポイントです。


2引数版の動き方をサンプルコードで確認する

まずは基本となる2引数版の動きを見てみましょう。

Stream.iterate(1, n -> n * 2)
      .limit(5)
      .forEach(System.out::println);

// 出力結果
// 1
// 2
// 4
// 8
// 16

このコードは「1から始めて、前の値を2倍した値を次々に生成する」という処理です。

ここで押さえておきたいのが、2引数版のStream.iterateは終了条件を持っていないという点です。要素はseedとfの組み合わせだけで決まるため、放っておくと無限に要素を作り続けます。そのため、limit()のような終端操作と組み合わせて使うのが基本になります。


試験で狙われる落とし穴:limit()を忘れるとどうなるか

ここがJava Gold 1Z0-826で特に問われやすいポイントです。

次のコードを見てください。

Stream.iterate(1, n -> n + 1)
      .forEach(System.out::println);

limit()がないため、このコードは1, 2, 3, 4…と要素を作り続け、プログラムが終了せずに無限ループの状態になります。

選択肢問題でよく出るのが「このコードを実行するとStackOverflowErrorが発生する」という誤った選択肢です。実際にはメソッド呼び出しのスタックが深くなるわけではなく、forEachの処理がひたすら繰り返されるだけなので、StackOverflowErrorにはなりません。正しくは「無限に処理が続き、プログラムが終了しない」です。


3引数版(Java 9以降)の動き方

Java 9から、終了条件を指定できる3引数版が追加されました。

Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
  • seed:最初の要素
  • hasNext:要素を生成し続けるかどうかを判定する条件(Predicate)
  • next:前の要素から次の要素を作る処理

3引数版では、hasNextの条件を満たさなくなった時点で自動的にストリームが終了するため、limit()を使わなくても有限のストリームになります。

Stream.iterate(1, n -> n < 50, n -> n * 2)
      .forEach(System.out::println);

// 出力結果
// 1
// 2
// 4
// 8
// 16
// 32

ここで注意したいのは、hasNextの判定は「その値を出力する前」に行われるという点です。上記の例では32の次は64になりますが、64はhasNextの条件(n < 50)を満たさないため出力されず、32で処理が終わります。「条件を満たさなくなった値そのものが出力されるかどうか」は、選択肢で混同を狙われやすいポイントです。


2引数版と3引数版の違いを整理する

ここまでの内容を表で整理します。

項目2引数版3引数版(Java 9以降)
構文iterate(seed, f)iterate(seed, hasNext, next)
終了条件なし(無限ストリーム)hasNextの判定で自動終了
limit()の必要性必須(忘れると無限ループ)不要
用途終端条件が不明、または後から決めたい場合終端条件があらかじめ決まっている場合
注意点StackOverflowErrorにはならない条件を満たさない値は出力されない

Stream.generateとの違い

Stream.iterateと混同しやすいメソッドに、Stream.generateがあります。

Stream<T> generate(Supplier<T> s)

決定的な違いは、要素同士に関係があるかどうかです。

  • Stream.iterate:前の要素を使って次の要素を作る(要素同士に依存関係がある)
  • Stream.generate:Supplierが呼ばれるたびに独立して値を返す(要素同士に依存関係がない)

乱数を生成する処理にはStream.generateが適しており、フィボナッチ数列のように前の値が次の値に影響する処理にはStream.iterateが適しています。

また、Stream.iterateは要素の生成が前の値に依存しているため、parallelStream()と組み合わせても並列化の効果が出にくい特徴があります。一方Stream.generateは要素同士が独立しているため、並列処理との相性がよい場合があります。この違いは並行処理の範囲と絡めて出題されることもあるため、知っておくと安心です。


まとめ

Stream.iterateは、Java Gold 1Z0-826のStream API分野で頻出する機能のひとつです。最後に重要なポイントを振り返ります。

  • 2引数版は終了条件を持たないため、limit()と組み合わせるのが基本
  • limit()を忘れると無限ループになる(StackOverflowErrorではない)
  • 3引数版(Java 9以降)はhasNextの条件で自動的に終了する
  • hasNextの判定は「出力する前」に行われるため、条件を満たさなくなった値は出力されない
  • Stream.generateとは「要素同士に依存関係があるかどうか」で使い分ける

サンプルコードを実際に手元で動かしながら、出力結果を一つひとつ確認してみてください。動かして体感した内容は、選択式の問題でも強い武器になります。