Java | Stream API の落とし穴と安全設計

Java Java
スポンサーリンク

ここでは、Stream APIの「落とし穴(注意点)」と「安全に使うための設計・ベストプラクティス」を、実務コード例とともに解説します。


Stream API の落とし穴と安全設計

落とし穴①:副作用(外部変数の変更)

❌ 悪い例

List<String> names = List.of("Alice", "Bob", "Charlie");
List<String> result = new ArrayList<>();

// forEachの中で外部リストを変更(副作用あり)
names.stream().forEach(name -> {
    if (name.startsWith("A")) {
        result.add(name); // ← 外部リストへの書き込み
    }
});
Java
  • 問題点forEach の中で外部の result を変更している。
    並列ストリーム (parallelStream) にすると、スレッド競合が起こり得る。
  • 症状:意図しない重複、順序の乱れ、ConcurrentModificationException など。

✅ 安全な書き方

List<String> names = List.of("Alice", "Bob", "Charlie");
List<String> result = names.stream()
    .filter(name -> name.startsWith("A"))
    .toList();  // 副作用なし、安全
Java

ポイント

  • Stream内で「外部の変数を変更しない」
  • filter / map / collect で「純粋な関数型処理」に徹する

落とし穴②:例外処理が書きづらい

❌ 悪い例

List<String> files = List.of("a.txt", "b.txt", "c.txt");

files.stream()
    .map(file -> Files.readString(Path.of(file))) // IOException が投げられる!
    .forEach(System.out::println);
Java
  • map 内でチェック例外(IOException)を投げるとコンパイルエラー。
    Stream APIはチェック例外を直接扱えない

✅ 安全な書き方(try-catchをラップ)

List<String> files = List.of("a.txt", "b.txt", "c.txt");

files.stream()
    .map(file -> {
        try {
            return Files.readString(Path.of(file));
        } catch (IOException e) {
            // ログ出力などの安全対応
            System.err.println("読み込み失敗: " + file + " → " + e.getMessage());
            return ""; // フォールバック値
        }
    })
    .filter(content -> !content.isEmpty())
    .forEach(System.out::println);
Java

ポイント

  • map 内で安全に try-catch する
  • 例外時のフォールバック値やロギング方針を決めておく

落とし穴③:null の扱い

❌ 悪い例

List<String> list = List.of("A", null, "B");
list.stream()
    .filter(s -> s.startsWith("A")) // NullPointerException!
    .forEach(System.out::println);
Java

✅ 安全な書き方

List<String> list = List.of("A", null, "B");
list.stream()
    .filter(Objects::nonNull)
    .filter(s -> s.startsWith("A"))
    .forEach(System.out::println);
Java

ポイント

  • Objects::nonNull でnull除外は定番
  • nullを混ぜない設計が最も安全(入力時チェック)

落とし穴④:ストリームの再利用

❌ 悪い例

Stream<String> stream = List.of("A", "B", "C").stream();
stream.forEach(System.out::println);
stream.filter(s -> s.equals("A")).count(); // IllegalStateException!
Java

✅ 安全な書き方

List<String> list = List.of("A", "B", "C");

list.stream().forEach(System.out::println);
long count = list.stream()
    .filter(s -> s.equals("A"))
    .count();
Java

ポイント

  • Streamは1回限り使い捨て
  • 再利用せず、都度 list.stream() を呼び直す

落とし穴⑤:parallelStream の誤用

❌ 悪い例

List<Integer> nums = IntStream.range(1, 10000).boxed().toList();

int sum = nums.parallelStream()
    .mapToInt(i -> {
        System.out.println(Thread.currentThread().getName()); // デバッグ出力混在
        return i;
    })
    .sum();
Java
  • 並列化で出力がぐちゃぐちゃに混在
  • I/Oやログを含む処理にparallelStreamを使うと非効率・不安定

✅ 安全な書き方

List<Integer> nums = IntStream.range(1, 10000).boxed().toList();

int sum = nums.parallelStream()
    .mapToInt(Integer::intValue)
    .sum(); // 純粋な計算ならOK
Java

ポイント

  • parallelStream()CPU計算専用
  • I/O処理・DBアクセスには不向き(並列数やスレッド安全性の問題)

実務向け安全設計まとめ

分類NG例安全な書き方
副作用外部リストにaddcollect()で新しいリスト作成
例外処理直接throwtry-catchで安全にラップ
null処理そのまま使用Objects::nonNullで除外
再利用同じstreamを再利用都度 .stream()
並列化parallel + I/O計算専用に限定

Java
スポンサーリンク
シェアする
@lifehackerをフォローする
スポンサーリンク
タイトルとURLをコピーしました