4

関数のスコープ外の変数は、作成時にどのように関数に取り込まれますか? 逆コンパイルしてみましたが、よくわかりませんでした。putfield を使用しているように見えました。putfield はオブジェクト参照へのポインタを作成しますか?

4

3 に答える 3

9

答えは「場合による」です。おそらく、scala 2.11 リリースでこれに大きな変更が加えられるでしょう。2.11 で単純なクロージャをインライン化できるようになることを願っています。

とにかく、現在の scala バージョン (以下の javap は scala 2.10.2 のもの) について答えてみましょう。以下は、val と var を使用する非常に単純なクロージャと、生成されたクロージャ クラスの javap 出力です。ご覧のとおり、var をキャプチャするか、val をキャプチャするかで大きな違いがあります。

val をキャプチャすると、クロージャー クラスにコピーとして渡されます (val であるため、これを行うことができます)。

var をキャプチャする場合は、var 自体の宣言を呼び出しサイトの場所で変更する必要があります。スタックにあるローカルの int の代わりに、var はscala.runtime.IntRef型のオブジェクトに変換されます。これは基本的にボックス化された整数ですが、変更可能な int フィールドがあります。

(これは、匿名の内部クラス内からフィールドを更新する場合に、サイズ 1 の final 配列を使用する Java のアプローチにいくぶん似ています)

これは、パフォーマンスに多少の影響を与えます。クロージャーで var を使用する場合、クロージャー オブジェクトと、var を含む xxxRef オブジェクトを生成する必要があります。1 つの平均的なことは、次のようなコード ブロックがある場合です。

var counter = 0
// some large loop that uses the counter

カウンターを別の場所にキャプチャするクロージャーを追加すると、ループのパフォーマンスが大幅に低下します。

要するに、 vals のキャプチャは通常大したことではありませんが、vars のキャプチャには十分注意してください。


object ClosureTest extends App {
  def test() {
    val i = 3
    var j = 0
    val closure:() => Unit = () => {
      j = i
    }
    closure()
  }
  test()
}

生成されたクロージャ クラスの javap コードは次のとおりです。

public final class ClosureTest$$anonfun$1 extends scala.runtime.AbstractFunction0$mcV$sp implements scala.Serializable {
  public static final long serialVersionUID;

  public final void apply();
Code:
  0: aload_0       
  1: invokevirtual #24                 // Method apply$mcV$sp:()V
  4: return        

  public void apply$mcV$sp();
Code:
  0: aload_0       
  1: getfield      #28                 // Field j$1:Lscala/runtime/IntRef;
  4: aload_0       
  5: getfield      #30                 // Field i$1:I
  8: putfield      #35                 // Field scala/runtime/IntRef.elem:I
  11: return        

  public final java.lang.Object apply();
Code:
  0: aload_0       
  1: invokevirtual #38                 // Method apply:()V
  4: getstatic     #44                 // Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
  7: areturn       

  public ClosureTest$$anonfun$1(int, scala.runtime.IntRef);
Code:
  0: aload_0       
  1: iload_1       
  2: putfield      #30                 // Field i$1:I
  5: aload_0       
  6: aload_2       
  7: putfield      #28                 // Field j$1:Lscala/runtime/IntRef;
  10: aload_0       
  11: invokespecial #48                 // Method scala/runtime/AbstractFunction0$mcV$sp."<init>":()V
  14: return        
}
于 2013-11-06T11:44:49.903 に答える
0

具体例を見てみましょう:

scala> var more = 1
more: Int = 1

scala> val f = (x: Int) => x + more
f: Int => Int = <function1>

この閉鎖はオープンタームです。

scala> f(1)
res38: Int = 2

scala> more = 2
more: Int = 2

scala> f(1)
res39: Int = 3

ご覧のとおり、クロージャにはキャプチャされたmore変数への参照が含まれています

于 2013-11-06T08:27:18.310 に答える