Stream の無限ストリーム(iterate, generate) — データ生成
無限ストリームは「必要なだけ動的に作る」ための道具です。Stream.iterate は「前の値から次を作る連続生成」、Stream.generate は「Supplier(供給者)が毎回新しい値を返す」仕組み。必ず limit や takeWhile 等で「止めどころ」を決めて使います。
無限ストリームの基本
- iterate(seed, f): 最初の値から、関数 f を繰り返し適用して次々生成(数列・連番・状態遷移に向く)。
- iterate(seed, predicate, f): Java 9+。predicate が false になったら停止(条件付き有限化)。
- generate(supplier): 毎回 supplier が値を返す(乱数・時刻・UUIDなど「前と無関係」な連続生成)。
- 止めるのが大前提: limit(n) や takeWhile(cond) を必ず使う。終端操作で消費しきらないと、延々と生成され続けます。
すぐ試せる基本例
iterate で連番を作る(limit で止める)
import java.util.stream.*;
IntStream.iterate(0, n -> n + 1)
.limit(10)
.forEach(System.out::println); // 0..9
JavaJava 9+ iterate(条件付きで自然に止める)
IntStream.iterate(0, n -> n <= 20, n -> n + 3)
.forEach(System.out::println); // 0,3,6,...,18
Javagenerate で乱数を作る
import java.util.*;
import java.util.stream.*;
new Random().ints() // 無限乱数の IntStream(Java標準)
.limit(5)
.forEach(System.out::println);
// Supplier版(例: 0.0〜1.0)
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
Java例題で理解する
例題1: 偶数だけを無限生成→先頭10件
IntStream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println); // 0,2,4,...,18
Java- ねらい: iterate は「前回から次回」を作る連続生成に最適。
例題2: Fibonacci 数列(Pair で状態遷移)
import java.util.stream.*;
record Pair(long a, long b) {}
Stream.iterate(new Pair(0, 1), p -> new Pair(p.b, p.a + p.b))
.limit(10)
.map(p -> p.a)
.forEach(System.out::println); // 0,1,1,2,3,5,8,13,21,34
Java- ねらい: 状態(前回2項)を持たせれば複雑な数列も簡潔に。
例題3: generate で時刻スタンプを作る
import java.time.Instant;
import java.util.stream.Stream;
Stream.generate(Instant::now)
.limit(3)
.forEach(System.out::println);
Java- ねらい: generate は「毎回独立に供給」する用途に最適。
例題4: takeWhile で途中停止(昇乗して閾値まで)
import java.util.stream.*;
Stream.iterate(1.0, x -> x * 1.5)
.takeWhile(x -> x <= 100.0)
.forEach(System.out::println); // 1.0, 1.5, 2.25, ... <= 100.0
Java- ねらい: 条件で自然停止。limit より直感的な場面もある。
よくある落とし穴と回避策
- 止め忘れ(limit/takeWhile なし): 終端操作が際限なく続き、CPUとメモリを浪費。必ず「有限化」する。
- 副作用だらけの generate: Supplier で外部状態を書き換えると再現性が落ちる。必要なら状態は限定的に、出力重視で純粋に。
- iterate のオーバーフロー: 数列が急成長する場合、int/long の範囲超過に注意。BigInteger を使う、takeWhile で閾値停止。
- 重すぎる処理を無限生成に乗せる: 遅延評価でも、終端で消費すると結局実行される。バッチサイズと停止条件を設計する。
- 並列化の誤用: 無限ストリームの parallel は分割粒度が扱いづらい。まず直列で動作確認→必要なら測定して並列化。
実用レシピ
連番IDを文字列にして収集
var ids = IntStream.iterate(1, n -> n + 1)
.limit(5)
.mapToObj(n -> "ID-" + n)
.toList(); // [ID-1..ID-5]
Java疑似データ行を生成(generate)
import java.util.UUID;
var lines = Stream.generate(() -> "%s,%s".formatted(UUID.randomUUID(), Math.random()))
.limit(3)
.toList();
Java一様乱数から整数範囲へ
var rnd = new java.util.Random();
IntStream.generate(() -> rnd.nextInt(100)) // 0..99
.limit(10)
.forEach(System.out::println);
Java時間間隔で値を増やす(状態持ち iterate)
record State(int step, double value) {}
Stream.iterate(new State(0, 1.0), s -> new State(s.step + 1, s.value * 2))
.limit(5)
.forEach(s -> System.out.println(s.step + ":" + s.value)); // 0:1.0, 1:2.0, ...
Javaテンプレート集(そのまま使える形)
- iterate(連続生成+有限化)
Stream.iterate(seed, f) // R型
.limit(n)
.forEach(action);
Java- Java 9+ iterate(条件停止)
Stream.iterate(seed, predicate, f)
.forEach(action);
Java- generate(供給者)
Stream.generate(supplier)
.limit(n)
.forEach(action);
Java- 数値特化(プリミティブストリーム)
IntStream.iterate(start, op).limit(n);
DoubleStream.generate(() -> ...).limit(n);
Java- 停止条件(takeWhile)
Stream.iterate(seed, f)
.takeWhile(cond)
.forEach(action);
Javaまとめ
- iterate は「前の値から次を作る」連続生成、generate は「毎回独立に供給」。
- 無限ストリームは「必ず有限化」するのが鉄則。limit、takeWhile、Java 9+ の条件付き iterate を使う。
- 数列・疑似データ・乱数・時刻など「動的生成」を直感的に書けるが、停止条件と型(オーバーフロー)に気を配ると失敗しない。
