Yai*_*adt 1 closures scala nested-function
scala 中的嵌套函数可以捕获父函数中的变量。
例如
def outer = {
var a = 0
def inner = {
a = 42
}
inner()
a
}
Run Code Online (Sandbox Code Playgroud)
在 C# 中,这是通过将所有捕获的变量存储在一个结构体上并在 byref 中传递该结构体来实现的。这避免了嵌套函数分配,除非您将其转换为函数对象。在sharplab中看到这个例子。
然而,在 scala 中,您不能通过 ref 传递变量,因此唯一可行的方法是将所有捕获的变量存储在一个对象上,然后传入该对象。
这是否意味着嵌套函数的每次调用都会在它捕获 scala 中的任何变量时进行分配?
The variable a
itself is still in the outer
method's stack frame, while the object that it refers to is allocated on the heap, as all Java objects are (even when a
is supposed to represent a primitive type).
By running javap -v
on that code of yours, we can see that a
is actually a final variable of type scala.runtime.IntRef
, which holds an integer field that can be updated. The nested method inner
is turned into a static method that accepts one argument of type IntRef
and sets its elem
field to 42. This is somewhat similar to the C# approach, but creates one object for each variable instead of a struct to hold all of them.
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
Run Code Online (Sandbox Code Playgroud)
Edit: Let's try it with a String
this time:
class ClosureTest {
def outer = {
var a = ""
def inner() = {
a = "42"
}
inner()
a
}
}
Run Code Online (Sandbox Code Playgroud)
Output from 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
Run Code Online (Sandbox Code Playgroud)
This time, since String
is not a primitive, the class ObjectRef
(which has a type parameter representing the wrapped value) is used, but it's still basically the same thing. Even though the JVM doesn't allow you to have ref
parameters like C# does, objects are still passed by reference, so the value of the object/primitive that a
holds can still be modified.
Here's a link to the only documentation I could find. There are lots of other classes, like BooleanRef
, FloatRef
, and also their volatile counterparts, like VolatileDoubleRef
, VolatileObjectRef
, etc. Each of these classes basically just have one mutable public field that the compiler uses when the "real" value of the captured variable is needed.
归档时间: |
|
查看次数: |
169 次 |
最近记录: |