执行捕获变量分配的嵌套函数
Do nested functions which capture variables allocate
scala 中的嵌套函数可以捕获父函数中的变量。
例如
def outer = {
var a = 0
def inner = {
a = 42
}
inner()
a
}
在 C# 中,这是通过将所有捕获的变量存储在结构中并在 byref 中传递该结构来实现的。这避免了嵌套函数分配,除非您将其转换为函数对象。请参阅 sharplab 中的 this example。
然而在 scala 中你不能通过 ref 传递变量,所以唯一可行的方法是将所有捕获的变量存储在一个对象上,然后传递该对象。
这是否意味着嵌套函数的每次调用都会分配,如果它捕获 scala 中的任何变量?
变量a
本身仍在outer
方法的堆栈帧中,而它所引用的对象分配在堆上,因为所有Java对象都是(甚至当 a
应该表示原始类型时)。
通过运行 javap -v
在你的那段代码中,我们可以看到 a
实际上是一个 scala.runtime.IntRef
类型的最终变量,它包含一个整数字段可以更新。嵌套方法 inner
变成了一个静态方法,它接受一个 IntRef
类型的参数并将其 elem
字段设置为 42。这有点类似于 C# 方法,但创建一个对象对于每个变量而不是结构来保存所有变量。
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:(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: (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
这一次,由于String
不是原始类型,所以使用class ObjectRef
(它有一个表示包装值的类型参数),但它仍然基本相同事物。尽管 JVM 不允许您像 C# 那样拥有 ref
参数,但对象仍然通过引用传递,因此 a
持有的 object/primitive 的值仍然可以修改。
这是我能找到的唯一文档的 link。还有很多其他 classes,如 BooleanRef
、FloatRef
,以及它们的易变对应物,如 VolatileDoubleRef
、VolatileObjectRef
等。每个 classes 基本上只有一个可变 public 字段,编译器在需要捕获变量的“真实”值时使用该字段。
scala 中的嵌套函数可以捕获父函数中的变量。
例如
def outer = {
var a = 0
def inner = {
a = 42
}
inner()
a
}
在 C# 中,这是通过将所有捕获的变量存储在结构中并在 byref 中传递该结构来实现的。这避免了嵌套函数分配,除非您将其转换为函数对象。请参阅 sharplab 中的 this example。
然而在 scala 中你不能通过 ref 传递变量,所以唯一可行的方法是将所有捕获的变量存储在一个对象上,然后传递该对象。
这是否意味着嵌套函数的每次调用都会分配,如果它捕获 scala 中的任何变量?
变量a
本身仍在outer
方法的堆栈帧中,而它所引用的对象分配在堆上,因为所有Java对象都是(甚至当 a
应该表示原始类型时)。
通过运行 javap -v
在你的那段代码中,我们可以看到 a
实际上是一个 scala.runtime.IntRef
类型的最终变量,它包含一个整数字段可以更新。嵌套方法 inner
变成了一个静态方法,它接受一个 IntRef
类型的参数并将其 elem
字段设置为 42。这有点类似于 C# 方法,但创建一个对象对于每个变量而不是结构来保存所有变量。
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:(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: (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
这一次,由于String
不是原始类型,所以使用class ObjectRef
(它有一个表示包装值的类型参数),但它仍然基本相同事物。尽管 JVM 不允许您像 C# 那样拥有 ref
参数,但对象仍然通过引用传递,因此 a
持有的 object/primitive 的值仍然可以修改。
这是我能找到的唯一文档的 link。还有很多其他 classes,如 BooleanRef
、FloatRef
,以及它们的易变对应物,如 VolatileDoubleRef
、VolatileObjectRef
等。每个 classes 基本上只有一个可变 public 字段,编译器在需要捕获变量的“真实”值时使用该字段。