匿名クラスでは、「無名」のネストされたクラスを実際に宣言しています。ネストされたクラスの場合、コンパイラーは、使用するすべての変数を引数として取るコンストラクターを使用して、新しいスタンドアロンのパブリッククラスを生成します(「名前付き」のネストされたクラスの場合、これは常に元の/囲んでいるクラスのインスタンスです)。これは、ランタイム環境にはネストされたクラスの概念がないため、ネストされたクラスからスタンドアロンクラスへの(自動)変換が必要なためです。
たとえば、次のコードを見てください。
public class EnclosingClass {
public void someMethod() {
String shared = "hello";
new Thread() {
public void run() {
// this is not valid, won't compile
System.out.println(shared); // this instance expects shared to point to the reference where the String object "hello" lives in heap
}
}.start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
}
これは機能しません。これは、コンパイラが内部で行うことです。
public void someMethod() {
String shared = "hello";
new EnclosingClass$1(shared).start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
元の匿名クラスは、コンパイラーが生成するいくつかのスタンドアロンクラスに置き換えられます(コードは正確ではありませんが、良い考えが得られるはずです)。
public class EnclosingClass$1 extends Thread {
String shared;
public EnclosingClass$1(String shared) {
this.shared = shared;
}
public void run() {
System.out.println(shared);
}
}
ご覧のように、スタンドアロンクラスは共有オブジェクトへの参照を保持します。Javaのすべてが値渡しであることを忘れないでください。そのため、EnclosingClassの参照変数「共有」が変更されても、それが指すインスタンスは変更されません。 、およびそれを指す他のすべての参照変数(無名クラスの1つであるEnclosing $ 1など)はこれを認識しません。これが、コンパイラがこの「共有」変数を最終として宣言することを強制する主な理由です。そのため、このタイプの動作は、すでに実行中のコードに組み込まれません。
これが、無名クラス内でインスタンス変数を使用するとどうなるかです(これは、問題を解決するために行うべきことであり、ロジックを「インスタンス」メソッドまたはクラスのコンストラクターに移動します)。
public class EnclosingClass {
String shared = "hello";
public void someMethod() {
new Thread() {
public void run() {
System.out.println(shared); // this is perfectly valid
}
}.start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
}
コンパイラーがコードを変更するため、これは正常にコンパイルされます。その結果、新しく生成されたクラスEnclosing $ 1は、インスタンス化されたEnclosingClassのインスタンスへの参照を保持します(これは単なる表現ですが、実行する必要があります)。
public void someMethod() {
new EnclosingClass$1(this).start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
public class EnclosingClass$1 extends Thread {
EnclosingClass enclosing;
public EnclosingClass$1(EnclosingClass enclosing) {
this.enclosing = enclosing;
}
public void run() {
System.out.println(enclosing.shared);
}
}
このように、EnclosingClassで参照変数 'shared'が再割り当てされ、これがThread#run()の呼び出しの前に発生すると、「other hello」が2回出力されます。これは、EnclosingClass $ 1#enclosing変数が参照を保持するためです。宣言されたクラスのオブジェクトに追加されるため、そのオブジェクトのすべての属性への変更は、EnclosingClass $ 1のインスタンスに表示されます。
この件の詳細については、この優れたブログ投稿(私が書いたものではありません)をご覧ください。http://kevinboone.net/java_inner.html