提问人:Yair Halberstadt 提问时间:6/28/2020 更新时间:6/29/2020 访问量:354
捕获变量的嵌套函数是否分配
Do nested functions which capture variables allocate
问:
scala 中的嵌套函数可以捕获父函数中的变量。
例如:
def outer = {
var a = 0
def inner = {
a = 42
}
inner()
a
}
在 C# 中,这是通过将所有捕获的变量存储在结构上,并在 byref 中传递该结构来实现的。这样可以避免嵌套函数分配,除非将其转换为函数对象。请参阅 sharplab 中的此示例。
然而,在 scala 中,你不能通过 ref 传递变量,所以唯一可行的方法是将所有捕获的变量存储在一个对象上,然后传入该对象。
这是否意味着嵌套函数的每次调用都会在捕获 scala 中的任何变量时进行分配?
答:
变量本身仍然在方法的堆栈帧中,而它引用的对象被分配在堆上,就像所有 Java 对象一样(即使它应该表示基元类型)。a
outer
a
通过运行您的代码,我们可以看到它实际上是 类型的最后一个变量,它包含一个可以更新的整数字段。嵌套方法将转换为静态方法,该方法接受一个 type 参数并将其字段设置为 42。这有点类似于 C# 方法,但为每个变量创建一个对象,而不是一个结构来保存所有变量。javap -v
a
scala.runtime.IntRef
inner
IntRef
elem
public int outer();
descriptor: ()I
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=2, args_size=1
0: iconst_0
1: invokestatic #16 // Method scala/runtime/IntRef.create:
(I)Lscala/runtime/IntRef;
4: astore_1
5: aload_1
6: invokestatic #20 // Method inner$1:(Lscala/runtime/IntRef;)V
9: aload_1
10: getfield #24 // Field scala/runtime/IntRef.elem:I
13: ireturn
编辑:这次让我们试试:String
class ClosureTest {
def outer = {
var a = ""
def inner() = {
a = "42"
}
inner()
a
}
}
输出自:javap
public java.lang.String outer();
descriptor: ()Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=2, args_size=1
0: ldc #12 // String
2: invokestatic #18 // Method scala/runtime/ObjectRef.create:(Ljava/lang/Object;)Lscala/runtime/ObjectRef;
5: astore_1
6: aload_1
7: invokestatic #22 // Method inner$1: (Lscala/runtime/ObjectRef;)V
10: aload_1
11: getfield #26 // Field
scala/runtime/ObjectRef.elem:Ljava/lang/Object;
14: checkcast #28 // class java/lang/String
17: areturn
这一次,由于不是原语,因此使用了类(具有表示包装值的类型参数),但它基本上仍然是一回事。即使 JVM 不允许像 C# 那样使用参数,对象仍然通过引用传递,因此仍然可以修改所保存的对象/基元的值。String
ObjectRef
ref
a
这是我能找到的唯一文档的链接。还有很多其他类,比如 ,以及它们的易变类,比如 、 等。这些类中的每一个基本上都只有一个可变的公共字段,当需要捕获的变量的“实际”值时,编译器会使用该字段。BooleanRef
FloatRef
VolatileDoubleRef
VolatileObjectRef
评论