Java 逆引き集 | イテレータ→ストリームの変換(StreamSupport) — 外部API統合

Java Java
スポンサーリンク

目的と前提

外部APIが Iterator(または Iterable)しか返さないときでも、Stream に変換すれば filter・map・collect などの強力な処理を宣言的に書けます。鍵になるのが StreamSupport と Spliterators の組み合わせです。Iterator を Spliterator に包んでから、順次または並列の Stream を生成します。StreamSupport は「低レベルのストリーム生成ユーティリティ」で、ライブラリ統合で真価を発揮します。

基本パターン(Iterator → Stream の最短ルート)

最小コードと意味

Iterator を Stream にする最短コードは次のとおりです。サイズ不明の Iterator を characteristics(データ特性フラグ)付きで Spliterator に包み、StreamSupport.stream でストリーム化します。

Iterator<String> it = externalApi.iterator();

Spliterator<String> sp = Spliterators.spliteratorUnknownSize(
    it,
    Spliterator.ORDERED | Spliterator.NONNULL // 分かっている特性だけ付与
);

Stream<String> stream = StreamSupport.stream(sp, false); // false=順次
List<String> result = stream
    .filter(s -> !s.isBlank())
    .map(String::trim)
    .toList();
Java

characteristics には ORDERED/DISTINCT/SORTED/SIZED/NONNULL/IMMUTABLE/CONCURRENT/SUBSIZED などがあり、分かっている特性のみ正しく指定します(不正確な宣言はバグの元)。サイズ不明なら spliteratorUnknownSize を使い、並列化したいなら第二引数を true にします。

Iterable の場合(より簡単)

Iterable からは spliterator を直接取り出して StreamSupport.stream に渡せます。

Iterable<User> iterable = externalApi.fetchUsers();
Stream<User> stream = StreamSupport.stream(iterable.spliterator(), false);
List<String> names = stream.map(User::name).toList();
Java

実戦例題(外部API統合での使いどころ)

ページングAPIの Iterator を Stream 化して集計

外部APIクライアントが「次ページを内部で取得する Iterator」を提供しているケース。Iterator の順次取得をそのまま Stream に乗せ、宣言的に処理できます。

Iterator<Order> it = client.listOrdersIterator(); // ページングを内包

Spliterator<Order> sp = Spliterators.spliteratorUnknownSize(
    it,
    Spliterator.ORDERED // 返却順が安定しているなら ORDERED を付ける
);

List<Order> top = StreamSupport.stream(sp, false)
    .filter(o -> o.total() >= 30_000)
    .sorted(Comparator.comparingInt(Order::score).reversed())
    .limit(100)
    .toList();
Java

ORDERED を付ける理由は「findFirst/limit の意味が依存する」ため。順序保証がない Iterator に ORDERED を付けるのは誤りなので注意します。

Closeable な Iterator を安全にクローズ(onClose と try-with-resources)

外部APIが「明示的に close が必要」な Iterator を返す場合、Stream#onClose を併用してリソース管理します。

CloseableIterator<String> it = client.openLogIterator(); // 仮の型

Spliterator<String> sp = Spliterators.spliteratorUnknownSize(it, 0);

try (Stream<String> s = StreamSupport.stream(sp, false).onClose(it::close)) {
    long errors = s.filter(l -> l.contains("ERROR")).count();
}
Java

Stream を try-with-resources で閉じると onClose が呼ばれ、Iterator のリソースも確実に解放されます。

並列化する場合の注意(サイズ不明 Spliterator)

サイズ不明の Spliterator は分割効率が低く、並列化しても速くならないことがあります。順次で十分な場合が多いですが、計算が重くて要素生成が軽いときは parallel を試しつつ計測で判断します。

Spliterator<Data> sp = Spliterators.spliteratorUnknownSize(it, Spliterator.ORDERED);
double sum = StreamSupport.stream(sp, true) // true=並列
    .mapToDouble(this::expensiveCompute)
    .sum();
Java

分割効率は Spliterator の characteristics に依存します。SIZED や SUBSIZED が正しく付与できるなら並列のメリットを得やすいですが、サイズが不明なら慎重に。

深掘りポイント(characteristics と正しさ)

characteristics の意味と指定の基準

  • ORDERED は「返却順が定義されている」場合のみ付けます(例:リスト・安定イテレータ)。
  • DISTINCT/SORTED は「集合的に保証できる」場合のみ。誤宣言は処理の意味を壊します。
  • SIZED は「総要素数が事前に分かる」場合に限る。未知なら spliteratorUnknownSize を使い、SIZED を付けない。
  • NONNULL は「絶対に null が混じらない」保証があるときのみ。
  • IMMUTABLE/CONCURRENT は並行更新・不変性の保証に関わるため、外部APIの契約に基づく厳密な判断が必要。

これらは Spliterator の分割方針やストリーム最適化にも関わるため、正しい指定が性能と正しさの両方に効きます。

StreamSupport の位置づけと安全な使い方

StreamSupport はライブラリ/統合層向けの低レベルAPIです。普通は collection.stream() で足りますが、外部APIから Iterator/Spliterator を受け取る場面では StreamSupport が「公式な生成窓口」になります。supplier 版のメソッドは Spliterator の遅延生成もでき、並列時の分割計画に役立つことがあります。

例外・キャンセルとリソース寿命

Iterator が外部接続(HTTP/DB)を握る場合、短絡操作(limit/findFirst)で早期終了すると未消費のページが残ります。必ず close を伝搬させる設計(onClose)にし、try-with-resources で明示的に閉じましょう。並列化時は外部APIに複数同時アクセスが起きないこと(安全かつ許容か)を事前確認します。

テンプレート(そのまま使える雛形)

Iterator を順次 Stream に

static <T> Stream<T> toStream(Iterator<T> it) {
    Spliterator<T> sp = Spliterators.spliteratorUnknownSize(it, 0);
    return StreamSupport.stream(sp, false);
}
Java

Iterator を並列 Stream に(慎重に)

static <T> Stream<T> toParallelStream(Iterator<T> it, int characteristics) {
    Spliterator<T> sp = Spliterators.spliteratorUnknownSize(it, characteristics);
    return StreamSupport.stream(sp, true);
}
Java

Iterable から Stream に

static <T> Stream<T> toStream(Iterable<T> it) {
    return StreamSupport.stream(it.spliterator(), false);
}
Java

CloseableIterator を onClose で自動クローズ

static <T> Stream<T> toStream(CloseableIterator<T> it) {
    Spliterator<T> sp = Spliterators.spliteratorUnknownSize(it, 0);
    return StreamSupport.stream(sp, false).onClose(it::close);
}
Java

よくある落とし穴と回避策

不正確な characteristics での誤動作

ORDERED/SORTED/DISTINCT の誤宣言は結果の意味を壊します。自信がないフラグは付けないのが安全です。わからないときは 0 を渡してから機能を積み上げます。

リソース未クローズ

外部APIの Iterator を Stream 化したら、必ず try-with-resources と onClose を組み合わせて寿命を管理します。中断時(例外・短絡)にも確実に close されるようにします。

無意味な並列化

サイズ不明・分割困難な Spliterator を並列化しても、オーバーヘッドが勝って遅くなることがあります。計測して必要時のみ並列化し、可能なら SIZED/SUBSIZED を正しく付けられる Spliterator を用意します。

まとめ

Iterator しか返さない外部APIでも、StreamSupport + Spliterators で安全に Stream 化できます。characteristics は「分かっていることだけ正確に」付与し、Closeable な Iterator は onClose と try-with-resources で寿命管理。並列は分割しやすいときだけ使い、そうでなければ順次で堅実に。StreamSupport はライブラリ統合のための低レベルAPIという位置づけを理解し、責務(生成・特性・資源管理)を丁寧に切り分けるのがプロの設計です。

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