Code Fix

上級

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による無限ストリームなど)を扱えるようにするための前提です。もし一度評価した結果を保持して再利用できる仕組みにすると、無限ストリームでは最後まで評価しようとして終わらなくなります。有限のコレクションのように全件をメモリに保持するのではなく、終端操作が要求した分だけを都度流して処理する設計のため、同じ結果を再利用する機構がそもそも存在しません。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)