如何在scala中实现闭包?

时间:2013-11-06 07:36:26

标签: scala closures decompiling

如果函数范围之外的变量在创建时被拉入函数中?我尝试反编译,但我无法理解它。它看起来像是使用了putfield。 putfield是否指向对象引用?

3 个答案:

答案 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,其构造函数包含两个字段,用于范围外的变量,例如:我和调用类的这个。