これに対する答えはわかっていると思いましたが、1時間ほど検索しても確認が見つかりませんでした。
このコードでは:
public class Outer {
// other code
private void method1() {
final SomeObject obj1 = new SomeObject(...);
final SomeObject obj2 = new SomeObject(...);
someManager.registerCallback(new SomeCallbackClass() {
@Override
public void onEvent() {
System.out.println(obj1.getName());
}
});
}
}
registerCallback
がそのパラメータをどこかに保存し、匿名サブクラスのオブジェクトがしばらく存続するとします。明らかに、このオブジェクトは、呼び出された場合にonEvent
が機能するように、obj1
への参照を維持する必要があります。
しかし、オブジェクトがobj2
を使用しない場合、オブジェクトはobj2
への参照を維持するので、オブジェクトが存続している間はobj2
をガベージコレクションできませんか? all visible final
(または事実上最終的な)ローカル変数とパラメーターがキャプチャされたため、オブジェクトが生きている限りGCを実行できないという印象を受けましたがどちらかと言えば何も言えません。
実装に依存しますか?
これに答えるJLSのセクションはありますか?私はそこで答えを見つけることができませんでした。
言語仕様では、匿名クラスがそれを囲むスコープから変数をキャプチャする方法についてはほとんど言及していません。
私が見つけることができる言語仕様の唯一の特に関連するセクションは JLS Sec 8.1. :です。
使用されているが内部クラスで宣言されていないローカル変数、仮パラメーター、または例外パラメーターは、finalとして宣言するか、実質的にfinalにする必要があります(§4.12.4)。そうしないと、使用を試みるとコンパイル時エラーが発生します。)
( 匿名クラスは内部クラスです )
匿名クラスがどの変数をキャプチャする必要があるか、またはそのキャプチャをどのように実装する必要があるかについては何も指定していません。
これから、実装は内部クラスで参照されていない変数をキャプチャする必要がないことを推測するのは合理的だと思います。しかし、それは彼らができないとは言いません。
obj1
のみがキャプチャされます。
論理的に、匿名クラスは次のような通常のクラスとして実装されます。
class Anonymous1 extends SomeCallbackClass {
private final Outer _outer;
private final SomeObject obj1;
Anonymous1(Outer _outer, SomeObject obj1) {
this._outer = _outer;
this.obj1 = obj1;
}
@Override
public void onEvent() {
System.out.println(this.obj1.getName());
}
});
匿名クラスは常に内部クラスであるため、必要がない場合でも、常に外部クラスへの参照を維持することに注意してください。コンパイラの新しいバージョンがそれを最適化したかどうかはわかりませんが、そうは思いません。これは、メモリリークの潜在的な原因です。
それの使用は次のようになります:
someManager.registerCallback(new Anonymous1(this, obj1));
ご覧のとおり、obj1
の参照値はコピー(値渡し)です。
宣言されたfinal
または事実上最終(Java 8+)であるかどうかにかかわらず、obj1
が最終である理由は技術的にありません。ただし、そうでない場合を除き、値の場合、コピーは変更されず、コピーが非表示のアクションであるため、値が変更されることを期待していたため、バグが発生します。プログラマーの混乱を防ぐために、彼らはobj1
を最終的なものにする必要があると判断しました。これにより、その動作について混乱することはありません。
私は、自分でチェックしなければならないというあなたの発言に興味があり、驚きました(なぜコンパイラがそのようなことをするのでしょうか?)。だから私はこのような簡単な例を作りました
public class test {
private static Object holder;
private void method1() {
final Object obj1 = new Object();
final Object obj2 = new Object();
holder = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println(obj1);
}
};
}
}
そして、method1
の次のバイトコードで結果が得られました
private method1()V
L0
LINENUMBER 8 L0
NEW Java/lang/Object
DUP
INVOKESPECIAL Java/lang/Object.<init> ()V
ASTORE 1
L1
LINENUMBER 9 L1
NEW Java/lang/Object
DUP
INVOKESPECIAL Java/lang/Object.<init> ()V
ASTORE 2
L2
LINENUMBER 10 L2
NEW test$1
DUP
ALOAD 0
ALOAD 1
INVOKESPECIAL test$1.<init> (Ltest;Ljava/lang/Object;)V
PUTSTATIC test.holder : Ljava/lang/Object;
つまり:
this
およびobj1
(ALOAD 1)を使用して新しいtest $ 1を作成しますですから、どうやってobj2
が匿名クラスインスタンスに渡されるという結論に達したのかわかりませんが、それは単に間違っていました。 IDKはコンパイラに依存している場合ですが、他の人が述べていることに関しては、不可能ではありません。
obj2は参照がないため、ガベージコレクションされます。匿名クラスを作成した場合でも、obj1への直接参照を作成したため、イベントがアクティブである限り、obj1はガベージコレクションされません。
最終的に行う唯一のことは、値を再定義できないことです。ガベージコレクタからオブジェクトを保護しません。