Java Tips | コレクション:Optional→Stream

Java Java
スポンサーリンク

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 と同じように mapflatMap に渡せる」ということです。
これにより、「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 で合流させたり、filtermap を一律にかけたりできる。

この「形を揃える」感覚が、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」に置き換えられないか眺めてみてください。

その小さな置き換えが、
「存在するかどうかも含めて、データを“流れ”として設計できるエンジニア」への、
確かな一歩になります。

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