如果函数范围之外的变量在创建时被拉入函数中?我尝试反编译,但我无法理解它。它看起来像是使用了putfield。 putfield是否指向对象引用?
答案 0 :(得分:8)
答案是“它取决于”。 scala 2.11版本可能会对此进行一些重大更改。希望2.11能够内联简单的闭包。
但无论如何,让我们尝试给出当前scala版本的答案(下面的javap来自scala 2.10.2)。下面是一个非常简单的闭包,它使用val和var,以及生成的闭包类的javap输出。正如您所看到的,如果捕获var或者捕获val,则存在主要差异。
如果捕获一个val,它只是作为副本传递给闭包类(你可以这样做,因为它是一个val)。
如果捕获var,则必须在呼叫站点位置更改var本身的声明。 var不是位于堆栈上的本地int,而是变成scala.runtime.IntRef类型的对象。这基本上只是一个盒装整数,但有一个可变的int字段。
(这有点类似于当你想从匿名内部类中更新字段时使用大小为1的最终数组的java方法)
这对性能有一些影响。在闭包中使用var时,必须生成闭包对象以及包含var的xxxRef对象。一个意思是,如果你有一个像这样的代码块:
var counter = 0
// some large loop that uses the counter
并添加一个在其他地方捕获计数器的闭包,循环的性能将显着降低。
所以最重要的是:捕获val通常不是什么大问题,但是要非常小心捕获变量。
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
}
答案 1 :(得分: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
变量
答案 2 :(得分:0)
以下是包含闭包的方法的字节码:
def run()
{
val buff = new ArrayBuffer[Int]();
val i = 7;
buff.foreach( a => { a + i } )
}
字节码:
public class com.anarsoft.plugin.scala.views.ClosureTest$$anonfun$run$1 extends scala.runtime.AbstractFunction1$mcII$sp implements scala.Serializable {
// Field descriptor #14 J
public static final long serialVersionUID = 0L;
// Field descriptor #18 I
private final int i$1;
public ClosureTest$$anonfun$run$1(com.anarsoft.plugin.scala.views.ClosureTest $outer, int i$1);
...
编译器生成一个新的ClosureTest $$ anonfun $ run $ 1,其构造函数包含两个字段,用于范围外的变量,例如:我和调用类的这个。