ラムダの変数は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は、ラムダが参照するローカル変数を実行時に共有するのではなく、ラムダを作った時点の値をコピーして持たせる設計にしました。値をコピーする以上、後から元の変数を書き換えられては辻褄が合わなくなるため、再代入されないことをコンパイラが要求します。