创建函数时,函数范围之外的变量是如何拉入函数的?我尝试反编译,但我无法理解它。看起来它使用了putfield。putfield 是否指向对象引用?
3 回答
答案是“视情况而定”。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 时,必须生成闭包对象以及 xxxRef 对象以包含 var。一个意思是,如果你有这样的代码块:
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
变量的引用
以下是包含闭包的以下方法的字节码:
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);
...
Compiler 生成一个新的 ClosureTest$$anonfun$run$1,它带有一个构造函数,该构造函数有两个字段用于作用域外的变量,例如调用类的 i 和 this。