Java 逆引き集 | flatMap を用いたネスト解除(OptionalやListのネスト) — 構造フラット化

Java Java
スポンサーリンク

flatMap を用いたネスト解除(OptionalやListのネスト) — 構造フラット化

Stream API の flatMap は「入れ子になった構造をフラットにする」ための中間操作です。
初心者がよくつまずくのは Optional や List の中にさらに List がある場合map ではネストが残りますが、flatMap を使うと一段階解除されて扱いやすくなります。


基本の違い(map vs flatMap)

  • map: 要素を変換するだけ → ネストが残る。
  • flatMap: 要素を「ストリームに展開」して結合 → ネストが解除される。

例: List<List<String>> を処理する場合

  • mapStream<List<String>>(まだ入れ子)
  • flatMapStream<String>(フラット化)

基本コード例

List のネスト解除

List<List<String>> nested = List.of(
    List.of("a","b"),
    List.of("c","d")
);

List<String> flat = nested.stream()
    .flatMap(list -> list.stream())
    .toList();

System.out.println(flat); // [a, b, c, d]
Java

Optional のネスト解除

List<Optional<String>> optionals = List.of(
    Optional.of("apple"),
    Optional.empty(),
    Optional.of("banana")
);

List<String> flat = optionals.stream()
    .flatMap(opt -> opt.stream()) // Optional を Stream に変換
    .toList();

System.out.println(flat); // [apple, banana]
Java

List の中に Optional

List<Optional<Integer>> nums = List.of(
    Optional.of(1),
    Optional.empty(),
    Optional.of(3)
);

int sum = nums.stream()
    .flatMap(Optional::stream) // 空は消え、値だけ流れる
    .mapToInt(Integer::intValue)
    .sum();

System.out.println(sum); // 4
Java

例題で理解する

例題1: ユーザーと複数のメールアドレス

record User(String name, List<String> emails) {}

List<User> users = List.of(
    new User("Tanaka", List.of("t@example.com","t2@example.com")),
    new User("Sato", List.of("s@example.com"))
);

List<String> allEmails = users.stream()
    .flatMap(u -> u.emails().stream())
    .toList();

System.out.println(allEmails);
// [t@example.com, t2@example.com, s@example.com]
Java
  • ポイント: ユーザーごとのリストをフラット化して全メールを一括取得。

例題2: ファイル読み込み結果(Optional)

List<Path> files = List.of(Paths.get("a.txt"), Paths.get("b.txt"));

List<String> contents = files.stream()
    .map(p -> {
        try {
            return Optional.of(Files.readString(p));
        } catch (IOException e) {
            return Optional.empty();
        }
    })
    .flatMap(Optional::stream) // 成功したものだけ流れる
    .toList();
Java
  • ポイント: 失敗したファイルは Optional.empty → 自然に除外される。

例題3: ネストした List の平方数

List<List<Integer>> nested = List.of(
    List.of(1,2),
    List.of(3,4)
);

List<Integer> squares = nested.stream()
    .flatMap(list -> list.stream())
    .map(n -> n*n)
    .toList();

System.out.println(squares); // [1,4,9,16]
Java

テンプレート集

  • List<List<T>> → List<T>
list.stream().flatMap(inner -> inner.stream()).toList();
Java
  • List<Optional<T>> → List<T>
list.stream().flatMap(Optional::stream).toList();
Java
  • Optional<T> → Stream<T>
opt.stream().forEach(...); // 値があれば1件、なければ0件
Java
  • List<User> → 全メールアドレス
users.stream().flatMap(u -> u.emails().stream()).toList();
Java

落とし穴と回避策

  • map と flatMap の混同: map はネストを残す。flatMap は展開する。違いを意識する。
  • Optional.stream は Java 9+: Java 8 では opt.map(Stream::of).orElseGet(Stream::empty) を使う。
  • null の扱い: Optional を使えば null 安全。flatMap で自然に除外できる。
  • 複数段ネスト: List<List<List<T>>> のような深いネストは段階的に flatMap で解除。

まとめ

  • flatMap は「ネスト解除」のための操作。List や Optional の中身を展開してフラットにできる。
  • Optional.stream を使うと「空は消え、値だけ流れる」ので null 安全。
  • ユーザーのメールリストやファイル読み込み結果など、実務でよくある「入れ子構造」をシンプルに扱える。

👉 練習課題: 「List<Optional<List<Integer>>>」を flatMap でフラット化し、全ての整数の合計を求めるコードを書いてみましょう。複数段のネスト解除が体感できます。

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