web-dev-qa-db-ja.com

なぜJava内部クラスには「最終的な」外部インスタンス変数が必要ですか?

final JTextField jtfContent = new JTextField();
btnOK.addActionListener(new Java.awt.event.ActionListener(){
    public void actionPerformed(Java.awt.event.ActionEvent event){
        jtfContent.setText("I am OK");
    }
} );

finalを省略すると、「別のメソッドで定義された内部クラス内の非最終変数jtfContentを参照できません」というエラーが表示されます。

匿名の内部クラスにアクセスするには、外部クラスのインスタンス変数をfinalにする必要があるのはなぜですか?

51
3m masr

さて、まずはリラックスして、銃を下ろしてください。

OK。さて、言語がそれを主張する理由は、内部クラス関数が提供するローカル変数へのアクセスを提供するためにチートするためです。ランタイムは、ローカル実行コンテキストのコピー(および必要に応じてなど)を作成するため、すべてをfinalにすることで、物事を正直に保つことができます。

それを行わなかった場合、オブジェクトの構築後にローカル変数の値を変更するコードですが、before実行される内部クラス関数は混乱を招き、奇妙になる可能性があります。

これはJava and "closures"の周りの多くの大騒ぎの本質です。


注:冒頭の段落は、OPの元の構成の一部大文字のテキストに関する冗談でした。

67
Pointy

匿名クラスのメソッドは、実際にはローカル変数とメソッドパラメーターにアクセスできません。むしろ、匿名クラスのオブジェクトがインスタンス化されると、finalローカル変数とオブジェクトのメソッドによって参照されるメソッドパラメーターのコピーがインスタンス変数として保存されますオブジェクト内。匿名クラスのオブジェクトのメソッドは、実際にこれらの隠されたインスタンス変数にアクセスします。 [1]

したがって、ローカルクラスのメソッドによってアクセスされるローカル変数とメソッドパラメーターは、オブジェクトがインスタンス化された後に値が変更されないように、finalとして宣言する必要があります。

[1] http://www.developer.com/Java/other/article.php/3300881/The-Essence-of-OOP-using-Java-Anonymous-Classes.htm

21
Jorge Ferreira

その理由は、Javaいわゆる「クロージャ」を完全にサポートしていない-その場合、finalは必要ないだろう-代わりにコンパイラをさせることでトリックを見つけた表示される機能を提供するために使用されるいくつかの隠し変数を生成します。

生成されたバイトコードを逆アセンブルすると、最終変数のコピーを含む奇妙な名前の隠された変数を含め、コンパイラーがそれをどのように行うかを見ることができます。

これは、言語を逆方向に曲げずに機能を提供するエレガントなソリューションです。


編集:For Java 8ラムダは、匿名クラスで以前行われていたことを行うためのより簡潔な方法を提供します。変数の制限も「最終」から「本質的に最終」に緩和されました。最終的に宣言する必要がありますが、扱われるが最終のようであれば(finalキーワードを追加してもコードはコンパイルされます)、使用できます。これは本当に素晴らしい変更です。

クラスの定義を取り巻く変数はスタック上に存在するため、内部クラス内のコードが実行されるとおそらく失われます(理由を知りたい場合は、スタックとヒープを検索します)。そのため、内部クラスは実際には包含メソッドで変数を使用せず、それらのコピーで構築されます。

これは、内部クラスを構築した後に包含メソッドの変数を変更しても、その値は期待していても内部クラスでは変更されないことを意味します。そこで混乱を防ぐために、Javaはそれらが最終であることを要求するので、それらを変更できないことを期待します。

7

Java 8final修飾子は外部インスタンス変数のオプションです。値は「実質的に最終」でなければなりません。回答を参照してください- 最終と実際の最終の違い

6
anstarovoyt