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件もなければ、opt は Optional.empty() になる」ことです。null ではなく、「空の Optional」として表現されます。
二つ目は、「ifPresent や orElse など、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
「最小値・最大値も、要素がなければ存在しない」
min や max も、結果を 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 を返す形」に書き換えられないか眺めてみてください。
その小さな書き換えが、
「存在しない可能性も含めて、結果を設計できるエンジニア」への、
確かな一歩になります。
