scala はコピーまたは参照によって変数の値を維持しますか?
たとえば、Ruby では「クロージャーは必要なすべての変数の有効期間を実際に延長します。それらはコピーされませんが、変数への参照は保持され、変数自体はガベージ コレクションの対象になりません (言語がガベージ コレクション) 閉鎖中」。【スコーキン】
jvm にはクロージャがなく、オブジェクトのみがあります。scala コンパイラーは、コード内でクロージャーが発生するたびに、(シグネチャの引数と結果の型に応じて) 適切な Function トレイトを実装する無名クラスを生成します。
たとえば、ある に対してl : List[Int]
と書くと、次l.map(i => i + 1)
のように変換されます。
class SomeFreshName extends Function[Int, Int] {
def apply(i: Int) = i + 1
}
l.map(new SomeFreshName())
この場合、i => i + 1 のように真のクロージャはありません。自由変数はなく、引数 i と定数だけです。
いくつかのローカル val、または同等に関数のパラメーターを閉じる場合、それらをコンストラクター パラメーターとしてクロージャー実装クラスに渡す必要があります。
sがl.map(i => s + i)
文字列パラメーターまたはメソッドのローカルである場合、それは行います
class SomeFreshName(s: String) extends Function[Int, String] {
def apply(i: Int) = s + i
}
l.map(new SomeFreshName(s))
コンストラクターに必要な数のパラメーターを渡します。
注 : s がメソッドのローカルではなくクラスのフィールドである場合s + i
、実際this.s + i
には であり、this
匿名クラスに渡されます。
ガベージ コレクターには特別なことは何もありません (ここでも、jvm はクロージャーを認識しません)。単純に、クロージャー オブジェクトは s への参照を持っているため、s は少なくともクロージャー オブジェクトと同じくらい存続します。
匿名クラスを使用する Java 言語でもまったく同じことが起こることに注意してください。匿名クラスが外側のメソッドのローカルを使用する場合、それらのローカルは匿名クラスのフィールドとしてサイレントに追加され、コンストラクターに渡されます。
Java では、これはローカルが である場合にのみ許可さfinal
れval
ますvar
。
実際、この実装では、クロージャーが作成されるとすぐに、変数の独自のコピーを持ち、他の変数を閉じます。それらを変更しても、それらの変更はメソッドに反映されません。それらがクロージャーで変更された場合、これはメソッドに反映されません。
あなたが書くと仮定します
var i = 0
l.foreach{a => println(i + ": " + a); i = i + 1}
println("There are " + i + " elements in the list")
前述の実装は次のようになります。
class SomeFreshName(var i: Int) extends Int => Unit {
def apply(a: Int) = println(i + ": " + a); i = i + 1
}
var i = 0
l.foreach(new SomeFreshName(i)
println("There are " + i + " elements in the list")
これを行うと、2 つの variable が存在します。1i
つはメソッド内にあり、もう 1 つは 内にありSomeFreshName
ます。SomeFreshName の 1 つだけが変更され、最後の println は常に 0 要素を報告します。
Scala は、クロージャーに含まれる var を参照オブジェクトに置き換えることで、彼の問題を解決します。与えられたクラス
class Ref[A](var content: A)
コードは最初に置き換えられます
val iRef = new Ref[Int](0)
l.foreach{a =>
println(iRef.content + ": " + a);
iRef.content += iRef.content + 1
}
println("There are " + i + " elements in the list")
もちろん、これはすべての var に対してではなく、たまたまクロージャーによって取得された var に対してのみ行われます。これにより、var が val に置き換えられ、実際の変数値がヒープに移動されました。これで、クロージャーは通常どおりに実行でき、機能します
class SomeFreshName(iRef: Ref[Int]) ...
Scala のクロージャもオブジェクトをディープ コピーせず、オブジェクトへの参照のみを保持します。さらに、クロージャーは独自のレキシカル スコープを取得しませんが、代わりに周囲のレキシカル スコープを使用します。
class Cell(var x: Int)
var c = new Cell(1)
val f1 = () => c.x /* Create a closure that uses c */
def foo(e: Cell) = () => e.x
/* foo is a closure generator with its own scope */
val f2 = foo(c) /* Create another closure that uses c */
val d = c /* Alias c as d */
c = new Cell(10) /* Let c point to a new object */
d.x = d.x + 1 /* Increase d.x (i.e., the former c.x) */
println(f1()) /* Prints 10 */
println(f2()) /* Prints 2 */
ガベージ コレクションについてコメントすることはできませんが、JVM のガベージ コレクターは、クロージャーがまだ参照されている限り、クロージャーによって参照されているオブジェクトを削除しないと思います。