Java 逆引き集 | Stream の無限ストリーム(iterate, generate) — データ生成

Java Java
スポンサーリンク

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
Java

Java 9+ iterate(条件付きで自然に止める)

IntStream.iterate(0, n -> n <= 20, n -> n + 3)
         .forEach(System.out::println); // 0,3,6,...,18
Java

generate で乱数を作る

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 を使う。
  • 数列・疑似データ・乱数・時刻など「動的生成」を直感的に書けるが、停止条件と型(オーバーフロー)に気を配ると失敗しない。
タイトルとURLをコピーしました