目的と前提
外部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();
Javacharacteristics には 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();
JavaORDERED を付ける理由は「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();
}
JavaStream を 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);
}
JavaIterator を並列 Stream に(慎重に)
static <T> Stream<T> toParallelStream(Iterator<T> it, int characteristics) {
Spliterator<T> sp = Spliterators.spliteratorUnknownSize(it, characteristics);
return StreamSupport.stream(sp, true);
}
JavaIterable から Stream に
static <T> Stream<T> toStream(Iterable<T> it) {
return StreamSupport.stream(it.spliterator(), false);
}
JavaCloseableIterator を 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という位置づけを理解し、責務(生成・特性・資源管理)を丁寧に切り分けるのがプロの設計です。
