web-dev-qa-db-ja.com

すべての最終変数は匿名クラスによってキャプチャされますか?

これに対する答えはわかっていると思いましたが、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のセクションはありますか?私はそこで答えを見つけることができませんでした。

17
ajb

言語仕様では、匿名クラスがそれを囲むスコープから変数をキャプチャする方法についてはほとんど言及していません。

私が見つけることができる言語仕様の唯一の特に関連するセクションは JLS Sec 8.1. :です。

使用されているが内部クラスで宣言されていないローカル変数、仮パラメーター、または例外パラメーターは、finalとして宣言するか、実質的にfinalにする必要があります(§4.12.4)。そうしないと、使用を試みるとコンパイル時エラーが発生します。)

匿名クラスは内部クラスです

匿名クラスがどの変数をキャプチャする必要があるか、またはそのキャプチャをどのように実装する必要があるかについては何も指定していません。

これから、実装は内部クラスで参照されていない変数をキャプチャする必要がないことを推測するのは合理的だと思います。しかし、それは彼らができないとは言いません。

12
Andy Turner

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を最終的なものにする必要があると判断しました。これにより、その動作について混乱することはありません。

10
Andreas

私は、自分でチェックしなければならないというあなたの発言に興味があり、驚きました(なぜコンパイラがそのようなことをするのでしょうか?)。だから私はこのような簡単な例を作りました

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;

つまり:

  • L0-最初のファイナルをidx1で保存します(ASTORE 1)
  • L1-idx 2で2番目のファイナルを保存します(これはanonクラスでは使用されません)(ASTORE 2)
  • L2-argumets(ALOAD 0)thisおよびobj1(ALOAD 1)を使用して新しいtest $ 1を作成します

ですから、どうやってobj2が匿名クラスインスタンスに渡されるという結論に達したのかわかりませんが、それは単に間違っていました。 IDKはコンパイラに依存している場合ですが、他の人が述べていることに関しては、不可能ではありません。

2
Antoniossss

obj2は参照がないため、ガベージコレクションされます。匿名クラスを作成した場合でも、obj1への直接参照を作成したため、イベントがアクティブである限り、obj1はガベージコレクションされません。

最終的に行う唯一のことは、値を再定義できないことです。ガベージコレクタからオブジェクトを保護しません。

0
Nertan Lucian