stream has already been operated upon の原因と直し方
Streamは1回しか終端操作できません。中間操作の遅延評価も合わせて解説します。
エラーメッセージの読み方
Exception in thread "main" java.lang.IllegalStateException:
main- スレッド名 — mainなら起動直後の処理で発生しています
java.lang- パッケージ — java.lang なら標準の基本例外です
IllegalStateException- 例外クラス — 何が起きたか。ここを検索するのが最短です
このエラーが出る典型パターン
パターン1
1 import java.util.List; 2 import java.util.stream.Stream; 3 4 public class Main { 5 public static void main(String[] args) { 6 Stream<Integer> s = List.of(1, 2, 3).stream(); 7 System.out.println(s.count()); 8 System.out.println(s .count()); ^ 9 } 10 }
Exception in thread "main" java.lang.IllegalStateException:
stream has already been operated upon or closedexited with code 1
Streamは1回しか終端操作できません。使い回さず、必要なたびに作り直します。
直し方: s を List.of(1, 2, 3).stream() にします。
広告
広告スロット(未設定)
パターン2
1 import java.util.List; 2 3 public class Main { 4 public static void main(String[] args) { 5 List<Integer> r = List.of(1, 2, 3).stream() 6 .filter(n -> n > 1) 7 .toList ; ^ 8 System.out.println(r); 9 } 10 }
Main.java:7: error: cannot find symbol
symbol: variable toList
toListはメソッドなので括弧が要ります。古い書き方では collect(Collectors.toList()) を使います。
直し方: toList を toList() にします。
パターン3
1 import java.util.List; 2 3 public class Main { 4 public static void main(String[] args) { 5 List.of(1, 2, 3).stream().filter(n -> n > 1) ; ^ 6 System.out.println("done"); 7 } 8 }
出力: done のみ(filterは中間操作なので何も起きません)
中間操作は終端操作が呼ばれるまで実行されません。これを遅延評価と呼びます。
直し方: (空) を .forEach(System.out::println) にします。
パターン4
1 import java.util.Arrays; 2 3 public class Main { 4 public static void main(String[] args) { 5 int[] nums = {1, 2, 3}; 6 var s = Arrays.stream(nums); 7 nums[0] = 100; ^ 8 System.out.println(s.sum()); 9 } 10 }
出力: 105期待: 6 (コンパイルは通っています)
Streamは終端操作の瞬間まで実際に値を読みません。作った後で元の配列を書き換えると、その変更が結果に反映されてしまいます。
直し方: 100 を 1 にします。
パターン5
1 import java.util.List; 2 import java.util.Map; 3 import java.util.stream.Collectors; 4 5 public class Main { 6 public static void main(String[] args) { 7 Map<Integer, String> m = List.of("a", "bb", "cc").stream() 8 .collect(Collectors.toMap(String::length, s -> s )); ^ 9 System.out.println(m); 10 } 11 }
Exception in thread "main" java.lang.IllegalStateException: Duplicate key 2exited with code 1
Collectors.toMapはキーが重複すると例外を投げます。マージ関数を渡すと、重複したときにどちらを残すか指定できます。
直し方: (空) を , (x, y) -> x にします。
よくある誤解
変数に入れると再利用したくなりますが、Streamは使い捨てです。コレクションとは性質が違います。
実務での勘所
Streamが使い捨てである設計は制約ではなく、無限に続く要素の列(Stream.iterateによる無限ストリームなど)を扱えるようにするための前提です。もし一度評価した結果を保持して再利用できる仕組みにすると、無限ストリームでは最後まで評価しようとして終わらなくなります。有限のコレクションのように全件をメモリに保持するのではなく、終端操作が要求した分だけを都度流して処理する設計のため、同じ結果を再利用する機構がそもそも存在しません。