Optional→Stream は「あるかもしれない1件」を“流れ”に乗せる技
Optional は「値があるかもしれない/ないかもしれない」を表す箱です。
Stream は「0件以上の要素の流れ」です。
この2つは相性がよくて、
「Optional の中身を、他の Stream 処理と同じ形で扱いたい」
という場面で効いてくるのが「Optional→Stream」です。
「1件あるかもしれない値」を、「0件または1件の Stream」に変換するイメージを持ってください。
基本:Optional を Stream に変えるとはどういうことか
「中身があれば1要素、なければ0要素」の Stream にする
まずはイメージから。
Optional<User> があるとします。
中身あり:Optional.of(user)
中身なし:Optional.empty()
これを Stream に変換すると、こうなります。
中身あり → Stream<User>(要素1件)
中身なし → Stream<User>(要素0件=空)
つまり、「Optional を“0件か1件のコレクション”として扱う」感覚です。
これができると、Optional を他の Stream と自然に合流させられます。
Java 9 以降の標準機能 Optional#stream
一番シンプルな書き方
Java 9 以降なら、Optional に stream() メソッドが用意されています。
import java.util.Optional;
import java.util.stream.Stream;
public class OptionalToStreamBasic {
public static void main(String[] args) {
Optional<String> opt1 = Optional.of("hello");
Optional<String> opt2 = Optional.empty();
Stream<String> s1 = opt1.stream(); // 要素1件の Stream
Stream<String> s2 = opt2.stream(); // 空の Stream
s1.forEach(System.out::println); // hello
s2.forEach(System.out::println); // 何も出ない
}
}
Javaここでの重要ポイントは二つです。
一つ目は、「opt.stream() が、“中身があれば1件、なければ0件”という Stream を返す」ことです。
null ではなく、あくまで「空の流れ」になります。
二つ目は、「この Stream は、他の Stream と同じように map や flatMap に渡せる」ということです。
これにより、「Optional だけ特別扱いしない」書き方ができます。
よくある使いどころ1:Optional を Stream の中で展開する
Optional を返す処理を flatMap でつなぐ
例えば、「ID からユーザーを検索する」メソッドが Optional を返すとします。
Optional<User> findUser(String id) { ... }
Java複数の ID から「見つかったユーザーだけ」を Stream で処理したいとき、
Optional→Stream がきれいにハマります。
import java.util.List;
List<String> ids = List.of("u001", "u002", "u003");
ids.stream()
.map(this::findUser) // Stream<Optional<User>>
.flatMap(Optional::stream) // Stream<User>(見つかったものだけ)
.forEach(u -> System.out.println(u.getId()));
Javaここでの重要ポイントは三つです。
一つ目は、「map で Optional を返すと Stream<Optional<User>> になってしまう」ことです。
このままだと扱いづらい。
二つ目は、「flatMap(Optional::stream) で、“中身ありの Optional だけを中身に展開し、empty は自然に落ちる”」ことです。Optional.empty() は要素0件の Stream になるので、そのまま消えてくれます。
三つ目は、「if (opt.isPresent()) ... のような分岐を書かずに、“流れの中で自然にフィルタ+展開”できている」ことです。
これが Optional→Stream の一番おいしいところです。
よくある使いどころ2:Optional なフィールドを展開する
「あるかもしれない子要素」をまとめて処理したい
例えば、Order が「請求書(Invoice)」を Optional で持っているとします。
class Order {
private Optional<Invoice> invoice;
public Optional<Invoice> getInvoice() { return invoice; }
}
Java全注文から、「請求書が存在するものだけ」を集めたいとき。
List<Order> orders = ...;
orders.stream()
.map(Order::getInvoice) // Stream<Optional<Invoice>>
.flatMap(Optional::stream) // Stream<Invoice>
.forEach(inv -> System.out.println(inv.getNumber()));
Javaここでの重要ポイントは、
「Optional なフィールドを、“null チェックの代わりに Stream で展開する”」という発想です。
if (order.getInvoice().isPresent()) { ... } を書き連ねる代わりに、
「全部 Stream に乗せて、Optional→Stream で flatten する」ことで、
コードの形が揃って読みやすくなります。
Java 8 で Optional#stream がない場合の書き方
自前で Optional→Stream を用意する
Java 8 には Optional#stream がないので、自分でユーティリティを用意します。
import java.util.Optional;
import java.util.stream.Stream;
public final class Optionals {
private Optionals() {}
public static <T> Stream<T> stream(Optional<T> opt) {
return opt.map(Stream::of)
.orElseGet(Stream::empty);
}
}
Java使い方はこうです。
ids.stream()
.map(this::findUser) // Stream<Optional<User>>
.flatMap(Optionals::stream) // Stream<User>
.forEach(...);
Javaここでの重要ポイントは二つです。
一つ目は、「opt.map(Stream::of) で“中身ありなら1要素の Stream”を作り、orElseGet(Stream::empty) で“なければ空 Stream”にしている」ことです。
二つ目は、「Java 9 以降なら Optional::stream に置き換えられる」ことです。
プロジェクトの Java バージョンに応じて、どちらを使うか決めてください。
Optional→Stream を使うときに意識したいこと
「Optional を“コレクションの仲間”として扱う感覚
Optional→Stream の本質は、
Optional を「0件か1件のコレクション」とみなすことです。
その感覚を持つと、設計の見え方が変わります。
「0件かもしれない1件」
→ Optional
→ Stream に乗せれば「0件か1件の流れ」
「0件以上の複数件」
→ List や Set
→ Stream に乗せれば「0件以上の流れ」
どちらも「流れ」にしてしまえば、flatMap で合流させたり、filter や map を一律にかけたりできる。
この「形を揃える」感覚が、Optional→Stream の一番大事なところです。
まとめ:Optional→Stream で身につけてほしい感覚
Optional→Stream は、
単に「便利メソッドを知る」話ではなく、
「“あるかもしれない1件”を、他のコレクションと同じ“流れ”として扱う設計」です。
Java 9 以降なら opt.stream()、Java 8 なら自前の Optionals.stream(opt) を用意する。map(...Optional...) → flatMap(Optional::stream) というパターンで、「見つかったものだけ」を自然に流す。
Optional なフィールドや検索結果を、if 文ではなく Stream の合流として扱う。
Optional を「0件か1件のコレクション」とみなし、Stream に乗せて“形を揃える”感覚を持つ。
あなたのコードのどこかに、if (opt.isPresent()) { ... } が何度も出てきているなら、
それを一度「Optional→Stream+flatMap」に置き換えられないか眺めてみてください。
その小さな置き換えが、
「存在するかどうかも含めて、データを“流れ”として設計できるエンジニア」への、
確かな一歩になります。
