3

私たちは皆、あなたがこのようなことをすることができないことを知っています:

int a = 7;
new Runnable() {
     public void run() {
         System.out.println(a);
     }
}.run();
...

...a決勝戦を行わずに。技術的な理由がわかります。これは、ローカル変数がスタック上に存在し、変更されないことがわかっていない限り、安全にコピーを作成できないためです。

しかし、私が理解するのに苦労しているのは、コンパイラに実装ハックがない理由です。そのため、上記の状況を検出すると、次のようにコンパイルされます。

int[] a = {7};
new Runnable() {
    public void run() {
        System.out.println(a[0]);
    }
}.run();
...

そうすれば、匿名の内部クラスからにアクセスして、必要に応じて実際に変更しても安全な位置にいます。もちろん、実際に変更した場合にのみ、このハックを実行する可能性がありますa。私が見る限り、これは比較的簡単に入れることができ、すべてのタイプで機能し、aどんな文脈からでも変えられるべきです。もちろん、上記の提案を変更して、複数の値に合成ラッパークラスを使用するか、もう少し効率的な別のアプローチを使用することもできますが、考え方は同じです。パフォーマンスにわずかな影響があると思いますが、特に内部でさらに最適化される可能性がある場合は、過度になるとは思えません。合成フィールドが特定の方法で壊れることに依存する特定の反射的な呼び出しを除いて、私は多くの不利な点を見ることができませんが、それが真剣に提案されたのを聞いたことがありません!理由はありますか?

4

3 に答える 3

2

匿名内部クラスが構築されると、その中で使用されるすべての変数の値がコピーされます。したがって、内部クラスが変数の値を変更しようとした場合、それは表示されません。たとえば、これが有効だったとします。

int a = 7;
Runnable r = new Runnable() {
    public void run() {
        a = 5;
    }
};
r.run();
System.out.println(a);

あなたはそれが5を印刷することを期待するかもしれません(それは確かにC#でそうするでしょう)。しかし、コピーが取られただけなので、実際には7を印刷します...許可されていれば、大きな変更はありません。

もちろん、Javaを変更して、値ではなく変数を実際にキャプチャすることもできます(C#は無名関数用だったため)。そのためには、「ローカル」変数を格納するための追加のクラスを自動的に作成し、メソッドと匿名の内部クラスの両方がその追加のクラスのインスタンスを共有するようにする必要があります。そうすれば、匿名の内部クラスはより強力になりますが、間違いなく理解が難しくなります。C#は、電力はあるが複雑なルートを選択することにしました。Javaは、制限的でありながら単純なアプローチを採用しました。

(カスタムクラスの代わりに配列を使用することは単一の変数に対して有効ですが、複数の変数が関係している場合はより無駄になります-あなたがそれを助けることができれば、すべての変数のラッパーオブジェクトを作成する必要はありません。)

少なくともC#ルールを使用すると、変数のキャプチャアプローチにはかなりの複雑さが伴うことに注意してください。例えば:

List<Runnable> runnables = new ArrayList<Runnable>();
int outer = 0;
for (int i = 0; i < 10; i++) {
    int inner = 0;
    runnables.add(new Runnable() {
        public void run() {
            outer++;
            inner++;
        }
    });
}

「内部」変数はいくつ作成されますか?ループのインスタンスごとに1つですか、それとも全体的に1つですか。基本的に、スコープはこの種のものの生活をトリッキーにします。実行可能ですが、注意が必要です。

于 2012-01-20T14:44:15.790 に答える
1

あなたが抱えているもう1つの問題(そしてこの制限がないgroovyで発生する可能性がある)は、非最終変数が変更される可能性があることです(そうでなければ、最終的なものにするのに問題はありません)

int a = 1; 
// use a in another thread, or potentially run it later
a = 2; 

スレッドが常にa=1を認識している場合、またはスレッドがa=2を認識している場合があります。

予測可能な匿名クラスを作成する唯一の方法は、ローカル変数が変更されない場合です。コンパイラーはより賢く、変数が変更できなくなったことを検出することができ、それによっていくつかの奇妙なケースが単純化されます。しかし、最も簡単な解決策は、ローカル変数がfinalであることを要求することです。

ところで:私のIDEに自動修正がある代替手段は、代わりに1つの配列を使用することです。

final int[] a = { 1 };
//  use a[0] in an anonymous class.
a[0] = 2;

醜いですが、値が変更される可能性のある匿名クラスを誤って作成する可能性は低くなります。

于 2012-01-22T08:17:45.927 に答える
0

匿名の内部クラスは、それらが作成されたスコープよりも長持ちする可能性があることを考えると、「ハック」する方法が簡単に実装できるかどうかはわかりません。内部クラスは、閉じたすべての変数のコピーを保持する必要があるため、スタック上に作成された変数を参照することはできません。

于 2012-01-20T14:51:44.030 に答える