前回はStream APIの reduce・groupingBy・partitioningBy など高度な集計操作を学びました。
今回は「OptionalとStreamの組み合わせ」について解説します。 以前それぞれ個別に学んだOptionalとStream APIですが、組み合わせることでより安全で読みやすいコードが書けるようになります。
OptionalとStreamを組み合わせる理由
以前学んだ Optional は「値があるかもしれないし、ないかもしれない」を表すクラスで、nullチェックを安全に書けます。Stream APIはコレクションの処理をパイプラインで書けます。
この2つを組み合わせると、たとえば「リストから条件に合う最初の要素を取得し、存在する場合だけ加工して返す」といった処理を、nullチェックやif文なしで安全に書けます。
// nullチェックを使った書き方
User user = findUser(id);
if (user != null) {
return user.getName().toUpperCase();
}
return "不明";
// OptionalとStreamを組み合わせた書き方
return findUser(id)
.map(User::getName)
.map(String::toUpperCase)
.orElse("不明");
サンプルコード(OptionalのStreamメソッド)
ファイル作成
「Java43」フォルダに OptionalStreamTest.java という名前でファイルを作成します

コード入力
以下のコードを入力します。
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class OptionalStreamTest {
public static void main(String[] args) {
// Optional.stream() でOptionalをStreamに変換(Java 9+)
Optional<String> present = Optional.of("ぱんまる");
Optional<String> empty = Optional.empty();
// 値ありのOptionalはstream()で1要素のStreamになる
System.out.println("値あり stream:");
present.stream().forEach(System.out::println);
// 値なしのOptionalはstream()で空のStreamになる
System.out.println("値なし stream:");
empty.stream().forEach(System.out::println);
System.out.println("(何も出力されません)");
// OptionalのリストからStream.flatMapで値ありだけ取り出す
List<Optional<String>> optionals = List.of(
Optional.of("ぱんまる"),
Optional.empty(),
Optional.of("たろう"),
Optional.empty(),
Optional.of("はなこ")
);
List<String> names = optionals.stream()
.flatMap(Optional::stream) // 値ありだけ取り出す
.collect(Collectors.toList());
System.out.println("\nOptionalリストから値ありだけ: " + names);
// mapとflatMapの違い
Optional<Optional<String>> nested = Optional.of(Optional.of("ネスト"));
Optional<String> mapped = nested.map(o -> o.orElse("なし"));
Optional<String> flatMapped = nested.flatMap(o -> o);
System.out.println("\nmapped: " + mapped);
System.out.println("flatMapped: " + flatMapped);
}
}

コンパイル
javac OptionalStreamTest.java

実行
java OptionalStreamTest
実行結果は次のようになります。
値あり stream:
ぱんまる
値なし stream:
(何も出力されません)
Optionalリストから値ありだけ: [ぱんまる, たろう, はなこ]
mapped: Optional[ネスト]
flatMapped: Optional[ネスト]

サンプルコード(実践的な組み合わせ例)
ファイル作成
「Java43」フォルダに OptionalStreamPractice.java という名前でファイルを作成します

コード入力
以下のコードを入力します。
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
public class OptionalStreamPractice {
record User(int id, String name, String email) {}
record Order(int userId, String product, int price) {}
static List<User> users = List.of(
new User(1, "ぱんまる", "panmaru@example.com"),
new User(2, "たろう", "taro@example.com"),
new User(3, "はなこ", "hanako@example.com")
);
static List<Order> orders = List.of(
new Order(1, "コーヒー", 350),
new Order(1, "ケーキ", 500),
new Order(2, "お茶", 150),
new Order(4, "ジュース", 200) // userId=4は存在しない
);
// IDからユーザーを検索(存在しない場合はOptional.empty())
static Optional<User> findUser(int id) {
return users.stream()
.filter(u -> u.id() == id)
.findFirst();
}
public static void main(String[] args) {
// ユーザー検索とメールアドレス取得
System.out.println("--- ユーザー検索 ---");
for (int id : new int[]{1, 2, 99}) {
String email = findUser(id)
.map(User::email)
.orElse("ユーザーが見つかりません");
System.out.println("ID " + id + ": " + email);
}
// 注文ユーザーの名前一覧(存在するユーザーのみ)
System.out.println("\n--- 注文ユーザー名 ---");
List<String> orderUserNames = orders.stream()
.map(order -> findUser(order.userId()))
.flatMap(Optional::stream) // 存在するユーザーだけ
.map(User::name)
.distinct() // 重複排除
.collect(Collectors.toList());
System.out.println(orderUserNames);
// ユーザーIDごとの注文合計金額
System.out.println("\n--- ユーザー別注文合計 ---");
Map<Integer, Integer> totalByUser = orders.stream()
.collect(Collectors.groupingBy(
Order::userId,
Collectors.summingInt(Order::price)));
totalByUser.forEach((userId, total) -> {
String userName = findUser(userId)
.map(User::name)
.orElse("不明なユーザー");
System.out.println(userName + ": ¥" + total);
});
// 最も高い注文金額とその商品
System.out.println("\n--- 最高金額の注文 ---");
orders.stream()
.max(java.util.Comparator.comparingInt(Order::price))
.ifPresent(order -> {
String userName = findUser(order.userId())
.map(User::name)
.orElse("不明");
System.out.println(userName + "の注文「"
+ order.product() + "」: ¥" + order.price());
});
}
}

コンパイル
javac OptionalStreamPractice.java

実行
java OptionalStreamPractice
実行結果は次のようになります。
--- ユーザー検索 ---
ID 1: panmaru@example.com
ID 2: taro@example.com
ID 99: ユーザーが見つかりません
--- 注文ユーザー名 ---
[ぱんまる, たろう]
--- ユーザー別注文合計 ---
ぱんまる: ¥850
たろう: ¥150
不明なユーザー: ¥200
--- 最高金額の注文 ---
ぱんまるの注文「ケーキ」: ¥500

コードの解説
Optional.stream() はJava 9から追加されたメソッドで、値がある場合は1要素のStream、値がない場合は空のStreamを返します。これにより flatMap(Optional::stream) で「Optionalのリストから値ありの要素だけ取り出す」操作がシンプルに書けます。
stream().filter(...).findFirst() はStreamの中から条件に合う最初の要素を Optional で返します。見つからなければ Optional.empty() が返るため、nullを返さなくて済みます。
.map(order -> findUser(order.userId())).flatMap(Optional::stream) は「注文からユーザーIDで検索し、存在するユーザーだけを取り出す」パターンです。map だと Stream<Optional<User>> になりますが、flatMap(Optional::stream) で Stream<User> に変換できます。
ifPresent() は値がある場合だけ処理を実行するため、最高金額の注文が存在する場合だけ出力します。Optional・Stream・Comparator・findUserの結果を組み合わせることで、nullチェックなしで安全にデータを扱えています。
まとめ
OptionalとStreamを組み合わせることで、nullチェックやif文を使わずに安全でシンプルなデータ処理が書けます。Optional.stream() でOptionalをStreamに変換し、flatMap(Optional::stream) でOptionalのリストから値ありだけ取り出すパターンは特によく使われます。findFirst() でStreamの検索結果をOptionalで受け取り、.map() や .orElse() でチェーンして処理する書き方を身につけることで、より洗練されたJavaコードが書けるようになります。
2026/08/03(月) ぱんまる


コメント