関数のスコープ外の変数は、作成時にどのように関数に取り込まれますか? 逆コンパイルしてみましたが、よくわかりませんでした。putfield を使用しているように見えました。putfield はオブジェクト参照へのポインタを作成しますか?
3 に答える
答えは「場合による」です。おそらく、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
}
具体例を見てみましょう:
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
変数への参照が含まれています