flatMap を用いたネスト解除(OptionalやListのネスト) — 構造フラット化
Stream API の flatMap は「入れ子になった構造をフラットにする」ための中間操作です。
初心者がよくつまずくのは Optional や List の中にさらに List がある場合。map ではネストが残りますが、flatMap を使うと一段階解除されて扱いやすくなります。
基本の違い(map vs flatMap)
- map: 要素を変換するだけ → ネストが残る。
- flatMap: 要素を「ストリームに展開」して結合 → ネストが解除される。
例: List<List<String>> を処理する場合
map→Stream<List<String>>(まだ入れ子)flatMap→Stream<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]
JavaOptional のネスト解除
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]
JavaList の中に 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 でフラット化し、全ての整数の合計を求めるコードを書いてみましょう。複数段のネスト解除が体感できます。
