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 本身的声明。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        
}
于 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 回答
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);

...

Compiler 生成一个新的 ClosureTest$$anonfun$run$1,它带有一个构造函数,该构造函数有两个字段用于作用域外的变量,例如调用类的 i 和 this。

于 2013-11-06T08:37:03.297 回答