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

Java Java
スポンサーリンク

Stream→Optional は「“0件かもしれない結果”を安全に受け取る」技

Stream は「0件以上の要素の流れ」です。
でも業務では、「この条件に合うものを“1件だけ”取りたい」「見つからないかもしれない」という場面が多いですよね。

そのときに、
「見つかったら値、見つからなければ null」
ではなく、
「見つかったら Optional に中身、見つからなければ Optional.empty」
という形で受け取るのが「Stream→Optional」です。

「結果があるかどうかも含めて、1つの値として扱う」ための入り口だと思ってください。


基本形1:findFirst で「先頭の1件」を Optional で受け取る

条件に合う最初の要素を取る

一番よく使うのが findFirst() です。

import java.util.List;
import java.util.Optional;

public class FindFirstExample {

    public static void main(String[] args) {
        List<String> names = List.of("山田", "佐藤", "鈴木");

        Optional<String> opt =
                names.stream()
                     .filter(n -> n.startsWith("佐"))
                     .findFirst();

        opt.ifPresent(System.out::println); // 佐藤
    }
}
Java

ここでの重要ポイントは二つです。

一つ目は、「条件に合う要素が1件もなければ、optOptional.empty() になる」ことです。
null ではなく、「空の Optional」として表現されます。

二つ目は、「ifPresentorElse など、Optional のAPIで“あるかもしれない結果”を安全に扱える」ことです。
opt.orElse("該当なし") のように、デフォルト値を決めることもできます。

String result =
        names.stream()
             .filter(n -> n.startsWith("高"))
             .findFirst()
             .orElse("見つからない");

System.out.println(result); // 見つからない
Java

基本形2:findAny で「どれか1件」を Optional で受け取る

並列処理で「順番にこだわらない1件」が欲しいとき

findAny() は、「どれでもいいから1件」を返す終端操作です。

Optional<String> opt =
        names.stream()
             .filter(n -> n.length() == 2)
             .findAny();
Java

順次 Stream では findFirst とほぼ同じ動きですが、
並列 Stream では「最初に見つかった1件」を返すため、順序は保証されません。

ここでの重要ポイントは、
「順番が意味を持たない処理なら、findAny の方が並列化と相性がいい」ことです。

ただし、初心者のうちは「基本は findFirst、順序不要で並列を意識するときに findAny」くらいの感覚で十分です。


集約結果を Optional で受け取る:min / max / reduce

「最小値・最大値も、要素がなければ存在しない」

minmax も、結果を Optional で返します。

import java.util.Comparator;
import java.util.List;
import java.util.Optional;

List<Integer> scores = List.of(80, 90, 75);

Optional<Integer> max =
        scores.stream()
              .max(Comparator.naturalOrder());

max.ifPresent(System.out::println); // 90
Java

要素が0件なら、「最大値は存在しない」ので Optional.empty() です。

同じように、reduce も「初期値なし」のバージョンは Optional を返します。

Optional<Integer> sum =
        scores.stream()
              .reduce((a, b) -> a + b);
Java

ここでの重要ポイントは、
「“0件かもしれない集約結果”は、Optional で受け取るのが自然」ということです。

「必ず1件以上ある」と言い切れるなら、初期値付き reduce(0, ...) などで Optional ではなく素の値を返す設計もできます。


自前ユーティリティで「Stream→Optional」を分かりやすくする

「条件に合う1件を探す」処理を名前付きにする

業務では、「ID で1件探す」「フラグが立っているものを1件だけ取る」など、
“検索して1件だけ Optional で返す”パターンが何度も出てきます。

これをユーティリティにすると、意図が読みやすくなります。

import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Stream;

public final class Streams {

    private Streams() {}

    public static <T> Optional<T> findFirst(
            Stream<T> stream,
            Predicate<? super T> predicate
    ) {
        if (stream == null) {
            return Optional.empty();
        }
        return stream.filter(predicate)
                     .findFirst();
    }
}
Java

使い方のイメージです。

Optional<String> opt =
        Streams.findFirst(
                names.stream(),
                n -> n.startsWith("佐")
        );
Java

ここでの重要ポイントは、
「“条件に合う1件を Optional で返す”という意図を、Streams.findFirst という名前で表現している」ことです。

生の stream.filter(...).findFirst() があちこちに散らばるより、
「ここは1件検索だな」と一目で分かるようになります。


Stream→Optional を使うときに意識したいこと

「結果がないことも“正常系”として扱う」

Stream→Optional の一番大事な感覚は、
「結果が見つからないことも、例外ではなく“普通のケース”として扱う」ことです。

findFirst()Optional.empty() を返すのは、
「エラー」ではなく「該当なし」という正常な結果です。

だからこそ、
orElse でデフォルト値を決める
orElseThrow で「ここは必ずあるべき」と宣言する
ifPresent で「あるときだけ処理する」

といった形で、「結果がない場合の振る舞い」をコードに刻むことができます。


まとめ:Stream→Optional で身につけてほしい感覚

Stream→Optional は、
単に「findFirst() を知る」話ではなく、
「“0件かもしれない1件”を、設計としてちゃんと表現する技術」です。

findFirst / findAny で「条件に合う1件」を Optional で受け取る。
min / max / 初期値なし reduce で、「要素がなければ結果もない」ことを Optional で表す。
ユーティリティやメソッド名で、「1件検索」「該当なしもあり得る」という意図をコードに刻む。
結果がないことを例外ではなく“正常系”として扱い、Optional のAPIで振る舞いを明示する。

あなたのコードのどこかに、
「Stream から1件取ってきて、null かもしれない値を返している」メソッドがあれば、
それを一度「Optional を返す形」に書き換えられないか眺めてみてください。

その小さな書き換えが、
「存在しない可能性も含めて、結果を設計できるエンジニア」への、
確かな一歩になります。

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