final Object o;
List l = new ArrayList(){{
// closure over o, in lexical scope
this.add(o);
}};
なぜo
最終宣言しなければならないのですか?可変変数を持つ他のJVM言語にこの要件がないのはなぜですか?
final Object o;
List l = new ArrayList(){{
// closure over o, in lexical scope
this.add(o);
}};
なぜo
最終宣言しなければならないのですか?可変変数を持つ他のJVM言語にこの要件がないのはなぜですか?
これはJVMの深さではなく、すべて構文糖衣レベルで発生します。その理由は、クロージャーを介して非最終変数をエクスポートすると、データレースの問題に対して脆弱になり、Javaは「ブルーカラー」言語として設計されているため、他の方法では飼いならされた安全なローカル変数の動作が驚くほど変化するためです。あまりにも「進んだ」と見なされました。
なぜそうしなければならないのかを論理的に推測するのは難しいことではありませんfinal
。
Javaでは、ローカル変数が匿名クラスにキャプチャされると、値によってコピーされます。これは、オブジェクトが現在の関数呼び出しよりも長く存続する可能性があるため(たとえば、返される可能性があるなど)、ローカル変数は現在の関数呼び出しの間だけ存続するためです。したがって、それまでに変数が存在しない可能性があるため、変数を単純に「参照」することはできません。Python、Ruby、JavaScriptなどの一部の言語では、ヒープなどの環境への参照を保持することにより、スコープがなくなった後に変数を参照できます。ただし、ローカル変数は関数のスタックフレームに割り当てられ、関数の呼び出しが完了すると破棄されるため、これをJVMで行うのは困難です。
これで、コピーされるため、変数のコピーが2つあります(この変数をキャプチャするクロージャがさらにある場合は、さらに多くなります)。それらが割り当て可能である場合は、もう一方を変更せずに一方を変更できます。たとえば、仮想的に:
Object o;
Object x = new Object(){
public String toString() {
return o.toString();
}
};
o = somethingElse;
System.out.println(x.toString()); // prints the original object, not the re-assigned one
// even though "o" now refers to the re-assigned one
スコープには変数が1つしかないo
ため、同じものを参照することが期待されます。上記の例では、に割り当てた後、オブジェクトからのo
後でのアクセスが新しい値を参照することを期待します。o
しかし、そうではありません。これは、プログラマーにとって驚くべき予想外のことであり、同じ変数の使用が同じものを参照するという原則に違反します。
したがって、この驚きを避けるために、彼らはあなたがどこにもそれに割り当てることができないことを義務付けています。つまり、である必要がありますfinal
。
もちろん、final
非final
変数から変数を初期化することもできます。final
また、クロージャー内では、変数を他の非に割り当てることができますfinal
。
Object a; // non-final
final Object o = a;
Object x = new Object(){
Object m = o; // non-final
public String toString() {
return ,.toString();
}
};
しかし、明示的に異なる変数を使用しているので、これはすべて良いことです。したがって、それが何をするかについては驚くことではありません。