【问题标题】:Do nested functions which capture variables allocate执行捕获变量的嵌套函数分配
【发布时间】:2020-10-18 18:59:12
【问题描述】:

scala 中的嵌套函数可以捕获父函数中的变量。

例如

def outer = {
    var a = 0
    def inner = {
        a = 42
    }
    inner()
    a
}

在 C# 中,这是通过将所有捕获的变量存储在一个结构中,并将该结构传递给 byref 来实现的。这避免了嵌套函数分配,除非您将其转换为函数对象。请参阅Sharplab 中的this example

然而,在 scala 中,您不能通过 ref 传递变量,因此唯一可行的方法是将所有捕获的变量存储在一个对象上,然后传入该对象。

这是否意味着如果嵌套函数在 scala 中捕获任何变量,它的每次调用都会分配?

【问题讨论】:

  • 变量是在创建嵌套函数时捕获的,而不是在调用它时捕获的。

标签: scala closures nested-function


【解决方案1】:

变量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$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

这一次,由于String 不是原始类型,所以使用了ObjectRef 类(它有一个表示包装值的类型参数),但它基本上还是一样的。尽管 JVM 不允许像 C# 那样拥有 ref 参数,但对象仍然通过引用传递,因此 a 持有的对象/原语的值仍然可以修改。

这是我能找到的唯一文档的link。还有很多其他类,比如BooleanRefFloatRef,以及它们的易失类,比如VolatileDoubleRefVolatileObjectRef等。这些类中的每一个基本上只有一个可变的公共字段,编译器在需要捕获变量的“真实”值。

【讨论】:

    猜你喜欢
    • 2018-10-09
    • 1970-01-01
    • 1970-01-01
    • 2014-09-27
    • 2014-01-10
    • 2014-12-27
    • 1970-01-01
    • 2011-09-06
    相关资源
    最近更新 更多