模試で「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とは「要素同士に依存関係があるかどうか」で使い分ける
サンプルコードを実際に手元で動かしながら、出力結果を一つひとつ確認してみてください。動かして体感した内容は、選択式の問題でも強い武器になります。