Scala关闭如何转换为Java对象?
我目前正在用不同的语言查看闭包实现。 然而,在涉及到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))
}
在反编译的源代码中,我看到Function1的一个匿名子类型,它应该是我的关闭。 但是apply()方法是空的,匿名类没有字段(可能存储闭包变量)。 我想反编译器没有设法从类文件中获取有趣的部分...
现在回答这些问题:
让我们分解一组例子,以便我们看到它们的不同之处。 (如果使用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
}
首先,让我们看看有什么method
:
public int method(int);
Code:
0: iload_1
1: aload_0
2: invokevirtual #17; //Method n:()I
5: iadd
6: ireturn
非常简单。 这是一种方法。 加载参数,调用n
的getter,add,return。 看起来就像Java。
function
如何? 它并不实际关闭任何数据,但它是一个匿名函数(称为Close$$anonfun$function$1
)。 如果我们忽视任何专业化,那么构造函数和应用是最感兴趣的:
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
所以,你加载一个“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
也就是说,它会检查以确保输入非空(即外部类非空)并将其保存在一个字段中。 现在,当它应用它,在拳击/拆箱包装后:
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
你会发现它使用该字段来引用父类,并调用n
的getter。 添加,返回,完成。 所以,闭包很容易:匿名函数构造函数只是将封闭类保存在一个专用字段中。
现在,如果我们不关闭内部变量,而是关闭方法参数呢? 这就是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
它在调用构造函数之前加载参数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
该参数保存在专用字段中。 由于我们不需要,因此不保留外部类的引用。 你也不应该为此感到惊讶:
public final int apply(int);
Code:
0: iload_1
1: aload_0
2: getfield #18; //Field m$1:I
5: iadd
6: ireturn
是的,我们只是加载存储的字段并进行数学计算。
我不确定你在做什么,看不到你的例子 - 对象有点棘手,因为他们同时拥有MyObject
和MyObject$
类,并且这两个方法之间的切换可能不直观。 但绝对适用于所有情况,整体而言,整个系统的工作方式与您期望的完全相同(在坐下来考虑真的很长时间之后)。
与Java伪匿名的匿名内部类不同,它不能修改看起来被关闭到其环境中的变量,Scala的闭包是真实的,因此闭包代码直接引用周围环境中的值。 这些值在从闭包中引用时会有不同的编译方式,因为这样做是可行的(因为方法代码无法从当前的任何激活框中访问当前的方法)。
相反,在Java中,它们的值被复制到内部类中的字段中,这就是为什么语言要求封闭环境中的原始值是final
,所以它们永远不会发生分歧。
由于所有Scala函数文本的闭包对封闭环境中的值的引用位于函数文字的apply()
方法的代码中,因此它们不会显示为为函数文本生成的实际Function
子类中的字段。
我不知道你是如何反编译的,但你如何做的细节可能解释了为什么你没有看到任何关于apply()
方法体的代码。
上一篇: How are Scala closures transformed to Java objects?
下一篇: Should I encapsulate blocks of functionality in anonymous JavaScript functions?