15

scala はコピーまたは参照によって変数の値を維持しますか?

たとえば、Ruby では「クロージャーは必要なすべての変数の有効期間を実際に延長します。それらはコピーされませんが、変数への参照は保持され、変数自体はガベージ コレクションの対象になりません (言語がガベージ コレクション) 閉鎖中」。【スコーキン】

4

2 に答える 2

26

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 では、これはローカルが である場合にのみ許可さfinalvalます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]) ...
于 2012-07-25T21:26:57.550 に答える
13

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 のガベージ コレクターは、クロージャーがまだ参照されている限り、クロージャーによって参照されているオブジェクトを削除しないと思います。

于 2012-07-25T20:12:46.530 に答える