Code Fix

上級

ラムダの変数はfinal/実質finalが必要という原因と直し方

ラムダが参照するローカル変数は再代入できません。配列で回避する定石も扱います。

エラーメッセージの読み方

Main.java:7: error: local variables referenced from a lambda expression must be final or effectively final
Main.java
ファイル名
7
行番号 — javacが異常に気づいた位置。原因の行とは限りません
error
種別 — コンパイルエラー。実行まで到達しません
local variables referenced from a lambda expression must be final or effectively final
内容 — javacが期待していたもの、または見つけられなかったもの

このエラーが出る典型パターン

パターン1

 1  import java.util.List;
 2  
 3  public class Main {
 4      public static void main(String[] args) {
 5          int count = 0;
 6          count = 1;        
                     ^
 7          List.of(1, 2, 3).forEach(n -> System.out.println(n + count));
 8      }
 9  }
Main.java:7: error: local variables referenced from a lambda expression must be final or effectively final

ラムダが参照するローカル変数は再代入できません。ラムダより前であっても、一度でも代入し直すと実質finalではなくなります。

直し方: count = 1; を (空) にします。

この問題を解いてみる →

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

パターン2

 1  import java.util.List;
 2  
 3  public class Main {
 4      public static void main(String[] args) {
 5          int[] sum = {0};
 6          List.of(1, 2, 3).forEach(n -> sum = new int[]{n});
                                                   ^
 7          System.out.println(sum[0]);
 8      }
 9  }
Main.java:6: error: local variables referenced from a lambda expression must be final or effectively final

配列変数そのものは再代入していないので、要素の書き換えは許されます。実質finalの回避策として使われます。

直し方: sum = new int[]{n} を sum[0] += n にします。

この問題を解いてみる →

パターン3

 1  import java.util.function.Function;
 2  
 3  public class Main {
 4      public static void main(String[] args) {
 5          Function<Integer, Integer> f = n =>  n * 2;
                                              ^
 6          System.out.println(f.apply(3));
 7      }
 8  }
Main.java:5: error: illegal start of expression

Javaのラムダの矢印は -> です。=> は他の言語の記法で、Javaでは通りません。

直し方: => を -> にします。

この問題を解いてみる →

パターン4

 1  import java.util.List;
 2  
 3  public class Main {
 4      public static void main(String[] args) {
 5          for (int n : List.of(1, 2, 3)) {
 6              n = n + 0;
                     ^
 7              Runnable r = () -> System.out.println(n);
 8              r.run();
 9          }
10      }
11  }
Main.java:7: error: local variables referenced from a lambda expression must be final or effectively final

forループの中でnに一度でも再代入すると、実質finalではなくなりラムダから参照できなくなります。

直し方: n = n + 0; を (空) にします。

この問題を解いてみる →

パターン5

 1  import java.util.List;
 2  
 3  public class Main {
 4      public static void main(String[] args) {
 5          int n = 10;
 6          List.of(1, 2, 3).forEach(n       -> System.out.println(n));
                                        ^
 7      }
 8  }
Main.java:5: error: variable n is already defined in method main(String[])

ラムダの引数名が外側のローカル変数と同じだと、二重定義として扱われエラーになります。

直し方: n を x にします。

この問題を解いてみる →

よくある誤解

配列で回避するのは常套手段ですが、そうしたくなったら設計を疑う合図でもあります。

実務での勘所

この制約があるのは、ラムダが生成された後もそのメソッドの外で(別のスレッドで実行される、後から呼び出されるコールバックとして保存されるなど)生き続ける可能性があるためです。ローカル変数はメソッドのスタック上にあり、メソッドが終了すれば消えてしまいます。そこでJavaは、ラムダが参照するローカル変数を実行時に共有するのではなく、ラムダを作った時点の値をコピーして持たせる設計にしました。値をコピーする以上、後から元の変数を書き換えられては辻褄が合わなくなるため、再代入されないことをコンパイラが要求します。

演習をはじめる

関連するエラー

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