Scala闭包如何转换为Java对象?

the*_*Dmi 18 closures decompiling scala

我目前正在研究不同语言的闭包实现.但是,当谈到Scala时,我无法找到关于如何将闭包映射到Java对象的任何文档.

有充分证据表明Scala函数映射到FunctionN对象.我假设对闭包的自由变量的引用必须存储在该函数对象的某处(例如,在C++ 0x中完成).

我还尝试使用scalac编译以下内容,然后使用JD反编译类文件:

object ClosureExample extends Application { 
  def addN(n: Int) = (a: Int) => a + n
  var add5 = addN(5)
  println(add5(20))
}
Run Code Online (Sandbox Code Playgroud)

在反编译的源代码中,我看到了一个匿名的Function1子类型,它应该是我的闭包.但是apply()方法是空的,并且匿名类没有字段(可能存储闭包变量).我想反编译器没有设法从类文件中获取有趣的部分......

现在问题:

  • 你知道转变是如何完成的吗?
  • 你知道它在哪里被记录吗?
  • 你有另外一个想法我怎么能解开这个谜团?

Rex*_*err 31

让我们分解一组示例,以便我们看到它们之间的区别.(如果使用RC1,请编译-no-specialization以使事情更容易理解.)

class Close {
  var n = 5
  def method(i: Int) = i+n
  def function = (i: Int) => i+5
  def closure = (i: Int) => i+n
  def mixed(m: Int) = (i: Int) => i+m
}
Run Code Online (Sandbox Code Playgroud)

首先,让我们看一下method:

public int method(int);
  Code:
   0:   iload_1
   1:   aload_0
   2:   invokevirtual   #17; //Method n:()I
   5:   iadd
   6:   ireturn
Run Code Online (Sandbox Code Playgroud)

非常直截了当.这是一种方法.加载参数,调用getter n,add,return.看起来就像Java.

怎么样function?它实际上并不关闭任何数据,但它一个匿名函数(称为Close$$anonfun$function$1).如果我们忽略任何特化,那么构造函数和apply最感兴趣:

public scala.Function1 function();
  Code:
   0:   new #34; //class Close$$anonfun$function$1
   3:   dup
   4:   aload_0
   5:   invokespecial   #35; //Method Close$$anonfun$function$1."<init>":(LClose;)V
   8:   areturn

public Close$$anonfun$function$1(Close);
  Code:
   0:   aload_0
   1:   invokespecial   #43; //Method scala/runtime/AbstractFunction1."<init>":()V
   4:   return

public final java.lang.Object apply(java.lang.Object);
  Code:
   0:   aload_0
   1:   aload_1
   2:   invokestatic    #26; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
   5:   invokevirtual   #28; //Method apply:(I)I
   8:   invokestatic    #32; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
   11:  areturn

public final int apply(int);
  Code:
   0:   iload_1
   1:   iconst_5
   2:   iadd
   3:   ireturn
Run Code Online (Sandbox Code Playgroud)

因此,您加载一个"this"指针并创建一个新的对象,该对象将封闭的类作为其参数.这是任何内部类的标准,真的.该函数不需要对外部类做任何事情,所以它只调用super的构造函数.然后,当调用apply时,你执行box/unbox技巧,然后调用实际的数学 - 也就是说,只需添加5.

但是如果我们在Close中使用变量的闭包呢?安装程序完全相同,但现在构造函数Close$$anonfun$closure$1如下所示:

public Close$$anonfun$closure$1(Close);
  Code:
   0:   aload_1
   1:   ifnonnull   12
   4:   new #48; //class java/lang/NullPointerException
   7:   dup
   8:   invokespecial   #50; //Method java/lang/NullPointerException."<init>":()V
   11:  athrow
   12:  aload_0
   13:  aload_1
   14:  putfield    #18; //Field $outer:LClose;
   17:  aload_0
   18:  invokespecial   #53; //Method scala/runtime/AbstractFunction1."<init>":()V
   21:  return
Run Code Online (Sandbox Code Playgroud)

也就是说,它会检查以确保输入为非null(即外部类为非null)并将其保存在字段中.现在到了应用它的时候,在装箱/取消装箱包装之后:

public final int apply(int);
  Code:
   0:   iload_1
   1:   aload_0
   2:   getfield    #18; //Field $outer:LClose;
   5:   invokevirtual   #24; //Method Close.n:()I
   8:   iadd
   9:   ireturn
Run Code Online (Sandbox Code Playgroud)

你看到它使用该字段来引用父类,并为其调用getter n.添加,返回,完成.因此,闭包很容易:匿名函数构造函数只是将封闭类保存在私有字段中.

现在,如果我们关闭不是内部变量,而是关闭方法参数呢?这是什么Close$$anonfun$mixed$1.首先,看看该mixed方法的作用:

public scala.Function1 mixed(int);
  Code:
   0:   new #39; //class Close$$anonfun$mixed$1
   3:   dup
   4:   aload_0
   5:   iload_1
   6:   invokespecial   #42; //Method Close$$anonfun$mixed$1."<init>":(LClose;I)V
   9:   areturn
Run Code Online (Sandbox Code Playgroud)

m在调用构造函数之前加载参数!所以构造函数看起来像这样并不奇怪:

public Close$$anonfun$mixed$1(Close, int);
  Code:
   0:   aload_0
   1:   iload_2
   2:   putfield    #18; //Field m$1:I
   5:   aload_0
   6:   invokespecial   #43; //Method scala/runtime/AbstractFunction1."<init>":()V
   9:   return
Run Code Online (Sandbox Code Playgroud)

其中该参数保存在私有字段中.不保留对外部类的引用,因为我们不需要它.申请时你也不应该感到惊讶:

public final int apply(int);
  Code:
   0:   iload_1
   1:   aload_0
   2:   getfield    #18; //Field m$1:I
   5:   iadd
   6:   ireturn
Run Code Online (Sandbox Code Playgroud)

是的,我们只是加载存储的字段并进行数学运算.

我不确定你做了什么,不能用你的例子看到这一点 - 对象有点棘手,因为它们有两个MyObjectMyObject$类,并且方法在两者之间以一种可能不直观的方式分开.但是应用绝对是适用的东西,整体而言,整个系统的工作方式与你期望的方式相同(在你坐下来思考它真的很难很长一段时间之后).


Ran*_*ulz 5

与Java的匿名内部类是伪闭包不同,并且不能修改看起来被封闭到其环境中的变量,Scala的闭包是真实的,因此闭包代码直接引用周围环境中的值.当从闭包中引用这些值时,这些值的编译方式不同,以使其成为可能(因为方法代码无法从当前激活帧以外的任何激活帧访问本地).

相反,在Java中,它们的值被复制到内部类中的字段,这就是语言需要封闭环境中的原始值的原因final,因此它们永远不会分歧.

因为所有Scala函数literal/closure对封闭环境中的值的引用都在函数文字apply()方法的代码中,所以它们不会显示为Function为函数文字生成的实际子类中的字段.

我不知道你是如何反编译的,但你如何这样做的细节可能解释了为什么你没有看到apply()方法体的任何代码.