a
ここでのみ最終的なものにすることができます。なんで?メソッドをプライベートメンバーとして保持せずに再割り当てa
するにはどうすればよいですか?onClick()
private void f(Button b, final int a){ b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { int b = a*5; } }); }
5 * a
クリックしたときに戻すにはどうすればよいですか?つまり、private void f(Button b, final int a){ b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { int b = a*5; return b; // but return type is void } }); }
15 に答える
コメントに記載されているように、Java 8 ではfinal
暗黙的に使用できるものもあり、これの一部は無関係になります。ただし、匿名の内部クラスまたはラムダ式で使用できるのは、実質的に最終的な変数のみです。
これは基本的に、Java がクロージャを管理する方法によるものです。
匿名内部クラスのインスタンスを作成すると、そのクラス内で使用されるすべての変数の値が、自動生成されたコンストラクターを介してコピーされます。これにより、コンパイラが「ローカル変数」の論理状態を保持するためにさまざまな追加の型を自動生成する必要がなくなります。たとえば、C# コンパイラが行うように... (C# が無名関数で変数をキャプチャすると、実際には変数がキャプチャされます。クロージャーは、メソッドの本体から見える方法で変数を更新できます。また、その逆も可能です。)
値が匿名の内部クラスのインスタンスにコピーされているため、変数がメソッドの残りの部分によって変更される可能性があるとしたら、奇妙に見えます。古い変数 (それが実際に起こっていることだからです...別の時間に撮影されたコピーで作業することになります)。同様に、匿名の内部クラス内で変更を加えることができる場合、開発者はそれらの変更が外側のメソッドの本体内に表示されることを期待するかもしれません。
変数を final にすると、これらの可能性がすべてなくなります。値はまったく変更できないため、そのような変更が表示されるかどうかを心配する必要はありません。メソッドと匿名の内部クラスが互いの変更を認識できるようにする唯一の方法は、何らかの記述の変更可能な型を使用することです。これは、囲んでいるクラス自体、配列、変更可能なラッパー型などの可能性があります。基本的に、あるメソッドと別のメソッド間の通信に少し似ています。あるメソッドのパラメーターに加えられた変更は、その呼び出し元には表示されませんが、パラメーターによって参照されるオブジェクトに加えられた変更は表示されます。
Java クロージャーと C# クロージャーのより詳細な比較に興味がある場合は、さらに詳しく説明した記事があります。この回答では、Java 側に焦点を当てたいと思いました :)
匿名クラスが外側のスコープでデータを更新できるようにするトリックがあります。
private void f(Button b, final int a) {
final int[] res = new int[1];
b.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
res[0] = a * 5;
}
});
// But at this point handler is most likely not executed yet!
// How should we now res[0] is ready?
}
ただし、同期の問題があるため、このトリックはあまり効果的ではありません。ハンドラーが後で呼び出される場合は、1) ハンドラーが別のスレッドから呼び出された場合は res へのアクセスを同期する必要があります。2) res が更新されたことを示す何らかのフラグまたは指示が必要です。
ただし、匿名クラスが同じスレッドですぐに呼び出された場合、このトリックは問題なく機能します。お気に入り:
// ...
final int[] res = new int[1];
Runnable r = new Runnable() { public void run() { res[0] = 123; } };
r.run();
System.out.println(res[0]);
// ...
匿名クラスは内部クラスであり、厳密な規則が内部クラス に適用されます(JLS 8.1.3) :
使用されているが内部クラスで宣言されていないローカル変数、仮メソッド パラメーター、または例外ハンドラー パラメーターは、final として宣言する必要があります。内部クラスで使用されているが宣言されていないローカル変数は、内部クラスの本体の前に確実に割り当てる必要があります。
jls または jvms に関する理由や説明はまだ見つかりませんが、コンパイラーが内部クラスごとに個別のクラス ファイルを作成し、このクラス ファイルでメソッドが宣言されていることを確認する必要があることはわかっています (バイトコードレベルで)少なくともローカル変数の値にアクセスできます。
(ジョンは完全な答えを持っています-JLSルールに興味があるかもしれないので、これを削除せずに残します)
戻り値を取得するクラス レベルの変数を作成できます。つまり
class A {
int k = 0;
private void f(Button b, int a){
b.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
k = a * 5;
}
});
}
K の値を取得して、必要な場所で使用できるようになりました。
あなたの理由の答えは:
ローカルの内部クラス インスタンスは Main クラスに関連付けられており、それを含むメソッドの最終的なローカル変数にアクセスできます。インスタンスがそれを含むメソッドの最終的なローカルを使用する場合、変数がスコープ外に出たとしても、変数はインスタンスの作成時に保持していた値を保持します (これは実質的に Java の粗雑な限定バージョンのクロージャーです)。
ローカル内部クラスはクラスまたはパッケージのメンバーではないため、アクセス レベルで宣言されません。(ただし、そのメンバーには通常のクラスと同様のアクセス レベルがあることに注意してください。)
Java では、変数はパラメーターとしてだけでなく、クラス レベルのフィールドとしても final にすることができます。
public class Test
{
public final int a = 3;
またはローカル変数として、
public static void main(String[] args)
{
final int a = 3;
匿名クラスから変数にアクセスして変更する場合は、その変数を囲んでいるクラスのクラスレベルの変数にすることができます。
public class Test
{
public int a;
public void doSomething()
{
Runnable runnable =
new Runnable()
{
public void run()
{
System.out.println(a);
a = a+1;
}
};
}
}
変数を final にして新しい値を与えることはできません。final
つまり、値は不変かつ最終的なものです。
最終的なものであるため、Java はそれをローカルの匿名クラスに安全にコピーできます。int への参照を取得していません(特に、Java の int のようなプリミティブへの参照を使用できないため、Objectsへの参照のみ)。
a の値を、匿名クラスの a という暗黙の int にコピーするだけです。
アクセスがローカルの final 変数のみに制限されている理由は、すべてのローカル変数にアクセスできるようにする場合、最初に内部クラスがアクセスできる別のセクションにコピーし、複数のコピーを維持する必要があるためです。変更可能なローカル変数は、データの一貫性を損なう可能性があります。final 変数は不変であるため、いくつコピーしてもデータの一貫性には影響しません。
メソッドの本体内で匿名内部クラスが定義されている場合、そのメソッドのスコープ内で final として宣言されているすべての変数は、内部クラス内からアクセスできます。スカラー値の場合、一度割り当てられると、最終変数の値は変更できません。オブジェクト値の場合、参照は変更できません。これにより、Java コンパイラは実行時に変数の値を「キャプチャ」し、コピーをフィールドとして内部クラスに格納できます。外側のメソッドが終了し、そのスタック フレームが削除されると、元の変数はなくなりますが、内側のクラスのプライベート コピーはクラス自体のメモリに残ります。
無名内部クラス内のメソッドは、それを生成したスレッドが終了した後でも呼び出される可能性があります。あなたの例では、内部クラスはイベントディスパッチスレッドで呼び出され、それを作成したスレッドと同じスレッドでは呼び出されません。したがって、変数のスコープは異なります。したがって、このような変数割り当てのスコープの問題を保護するには、それらを final と宣言する必要があります。
private void f(Button b, final int a[]) {
b.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
a[0] = a[0] * 5;
}
});
}
ジョンは実装の詳細に答えているので、別の可能な答えは、JVM が彼のアクティブ化を終了した記録への書き込みを処理したくないということです。
ラムダが適用されるのではなく、ある場所に保存され、後で実行されるユースケースを考えてみましょう。
Smalltalk では、このような変更を行うと不正なストアが発生したことを覚えています。
たぶん、このトリックはあなたにアイデアを与えるでしょう
Boolean var= new anonymousClass(){
private String myVar; //String for example
@Overriden public Boolean method(int i){
//use myVar and i
}
public String setVar(String var){myVar=var; return this;} //Returns self instane
}.setVar("Hello").method(3);