これは、匿名クラスが実装されていること方法が原因で発生します。あなたは、コードにわずかな変更を行い、その後、デコンパイル場合は、これを見ることができます:
final Runnable other = null;
final Runnable example = new Runnable() {
@Override
public void run() {
System.out.println(other);
}
};
すなわち、匿名クラスが別のローカル変数を参照します。これは今コンパイルされます。私たちは、javap
を使用してコンパイルし、匿名クラスのインタフェースを見ることができます:
final class Example$1 implements java.lang.Runnable {
final java.lang.Runnable val$other;
Example$1(java.lang.Runnable);
public void run();
}
(Example$1
は、Javaが内部で匿名クラスを参照することにより、名前ですが)。
これは、コンパイラがRunnable
パラメータを取る匿名クラスにコンストラクタを追加したことを示しています。 val$other
というフィールドもあります。このフィールドのこの名前は、このフィールドがother
ローカル変数に関連していることを示すはずです。
あなたはさらにバイトコードに掘ると、このパラメータはval$other
に割り当てられていることがわかります。
Example$1(java.lang.Runnable);
Code:
0: aload_0
// This gets the parameter...
1: aload_1
// ...and this assigns it to the field val$other
2: putfield #1 // Field val$other:Ljava/lang/Runnable;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return
だから、何これは示していることは、匿名クラスは自分囲む範囲から変数にアクセスする方法は次のとおりです。彼らは単に建設時に値を渡しただけです。
これは、コンパイラが質問のようなコードを書くことをなぜ止めるのかを示しているはずです。Runnable
への参照を匿名クラスに渡して構築する必要があります。しかし、Javaは次のコードを評価する方法:
final Runnable example = new Runnable() { ... }
は完全に最初の右辺を評価した後、左側の変数に代入することです。このコードがあるので、問題ではないexample
が以前に宣言されていないこと
final Runnable example = new Example$1(example);
:しかし、Runnable$1
の生成コンストラクタに渡すために、右辺の変数の値を必要とします意味的に同じ: - したがって、それは、コンストラクタの引数として使用される前に、しかし、example
は、値が割り当てられていない
final Runnable example;
example = new Example$1(example);
ので、あなたが得るエラーは、変数が解決できないということではありませんコンパイラエラー。
これは単に実装の詳細であると主張される可能性があります:run()
メソッドの前に呼び出すことができるという方法がないとして、それは、引数はコンストラクタに渡さなければならないことは問題ではないはずです割り当て。
実は、それは真実ではない:あなたが代入する前にrun()
を呼び出すことができ、次のように匿名クラス内example
を参照が許可された場合
final Runnable example = new Runnable() {
Runnable runAndReturn() {
run();
return this;
}
@Override public void run() {
System.out.println(example);
}
}.runAndReturn();
は、あなたはこれを書くことができるだろう。したがって、その変数の参照は許可されません。
を!逆コンパイルされた 'Example $ 1'クラスが' other'を 'final'にする必要があります。もしそうでなければ、 'Example $ 1'は潜在的に' other'の時代遅れのコピーを扱っている可能性があります。 –