2016-02-04 21 views
5

self-answered questionVariable 'snackbar' might not have been initializedからインスパイアされています。私は、その特定の質問とは別に、より良い詳細が追加されていると感じました。匿名クラスで「変数の例が初期化されていない可能性があります」

なぜ次のコードをコンパイルできないのですか?

public class Example { 
    public static void main(String[] args) { 
    final Runnable example = new Runnable() { 
     @Override 
     public void run() { 
     System.out.println(example); // Error on this line 
     } 
    }; 
    } 
} 

コンパイルエラー:

error: variable example might not have been initialized 

答えて

8

これは、匿名クラスが実装されていること方法が原因で発生します。あなたは、コードにわずかな変更を行い、その後、デコンパイル場合は、これを見ることができます:

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(); 

は、あなたはこれを書くことができるだろう。したがって、その変数の参照は許可されません。

+0

を!逆コンパイルされた 'Example $ 1'クラスが' other'を 'final'にする必要があります。もしそうでなければ、 'Example $ 1'は潜在的に' other'の時代遅れのコピーを扱っている可能性があります。 –

4

あなたは「この」コンパイルエラーを回避するために使用することができます。本当に素敵な

final Runnable example = new Runnable() { 
    @Override 
    public void run() { 
    System.out.println(this); // Use "this" on this line 
    } 
}; 
関連する問題