【发布时间】:2013-11-17 08:34:37
【问题描述】:
函数作用域之外的变量在创建时是如何拉入函数的?我尝试反编译,但我无法理解它。看起来它使用了putfield。 putfield 是否指向对象引用?
【问题讨论】:
标签: scala closures decompiling
函数作用域之外的变量在创建时是如何拉入函数的?我尝试反编译,但我无法理解它。看起来它使用了putfield。 putfield 是否指向对象引用?
【问题讨论】:
标签: scala closures decompiling
答案是“视情况而定”。 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 字段。
(这有点类似于 java 方法,当您想从匿名内部类中更新字段时使用大小为 1 的最终数组)
这对性能有一些影响。在闭包中使用 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);
...
编译器生成一个新的 ClosureTest$$anonfun$run$1,它带有一个构造函数,其中包含两个字段用于范围外的变量,例如i 和 this 的调用类。
【讨论】: