在定义闭包时,Scala如何维护变量的值?

Bil*_*llz 15 closures scala

scala是否通过复制或引用来维护变量的值?

例如,在Ruby中,闭包实际上会延长它所需的所有变量的生命周期.它不会复制它们,但会保留对它们的引用,并且变量本身不符合垃圾收集的条件(如果语言有垃圾收集)而关闭是围绕".[SKORKIN]

Did*_*ont 24

jvm没有闭包,它只有对象.对于代码中闭包的每次出现,scala编译器生成实现适当的Function特征(取决于签名的参数和结果类型)的匿名类.

例如,如果对某些人l : List[Int],你写l.map(i => i + 1),它将被转换为

class SomeFreshName extends Function[Int, Int] {
  def apply(i: Int) = i + 1
}

l.map(new SomeFreshName())
Run Code Online (Sandbox Code Playgroud)

在这种情况下,没有真正的闭包,因为在i => i + 1中,没有自由变量,只有参数i和常量.

如果你关闭一些本地的val,或等价的函数的参数,它们将必须作为构造函数参数传递给闭包实现类:

对于l.map(i => s + i)其中s是字符串参数或方法的本地,它将执行

class SomeFreshName(s: String) extends Function[Int, String] {
  def apply(i: Int) = s + i
}
l.map(new SomeFreshName(s))
Run Code Online (Sandbox Code Playgroud)

根据需要在构造函数中传递尽可能多的参数.

注意:如果s是类的字段而不是方法的本地字段,那么s + i事实上this.s + i,this它将被传递给匿名类.

垃圾收集器中没有什么特别的(再次,jvm不知道闭包),简单地说,因为闭包对象有一个对s的引用,s将至少与闭包对象一样长.

请注意,使用匿名类的java语言中完全相同.当匿名类使用封闭方法的locals时,这些locals以静默方式添加为匿名类的字段,并在构造函数中传递.

在java中,只有当本地是finalscala 时才允许这样做val,而不是scala var.

实际上,通过这种实现,一旦创建了闭包,它就有自己的变量副本.如果它修改它们,那么这些修改将不会反映在方法中.如果在闭包中修改它们,则不会在方法中反映出来.

假设你写

var i = 0
l.foreach{a => println(i + ": " + a); i = i + 1}
println("There are " + i + " elements in the list")
Run Code Online (Sandbox Code Playgroud)

之前描述的实现将是

class SomeFreshName(var i: Int) extends Int => Unit {
   def apply(a: Int) = println(i + ": " + a); i = i + 1
}
var i = 0
l.foreach(new SomeFreshName(i)
println("There are " + i + " elements in the list") 
Run Code Online (Sandbox Code Playgroud)

这样做,会有两个变量i,一个在方法中,一个在SomeFreshName.只修改SomeFreshName中的那个,并且最后一个println将始终报告0个元素.

Scala通过用引用对象替换闭包中的var来解决他的问题.给一个班级

class Ref[A](var content: A)
Run Code Online (Sandbox Code Playgroud)

代码首先被替换为

val iRef = new Ref[Int](0)
l.foreach{a => 
  println(iRef.content + ": " + a); 
  iRef.content += iRef.content + 1
}
println("There are " + i + " elements in the list")
Run Code Online (Sandbox Code Playgroud)

这当然只对闭包发生的var而不是每个var都有效.这样做,var已被val替换,实际的变量值已被移入堆中.现在,关闭可以照常完成,并且可以正常工作

class SomeFreshName(iRef: Ref[Int]) ...
Run Code Online (Sandbox Code Playgroud)

  • +1提及`Ref`.这是我希望很少有人提及的技术细节. (3认同)

Mal*_*off 12

Scala中的闭包也不会深层复制对象,它们只会保留对象的引用.而且,闭包不会获得它自己的词法范围,而是使用周围的词法范围.

class Cell(var x: Int)
var c = new Cell(1)

val f1 = () => c.x /* Create a closure that uses c */

def foo(e: Cell) = () => e.x
  /* foo is a closure generator with its own scope */

val f2 = foo(c)    /* Create another closure that uses c */

val d = c          /* Alias c as d */
c = new Cell(10)   /* Let c point to a new object */
d.x = d.x + 1      /* Increase d.x (i.e., the former c.x) */

println(f1())      /* Prints 10 */
println(f2())      /* Prints 2 */
Run Code Online (Sandbox Code Playgroud)

我不能评论垃圾收集,但我假设JVM的垃圾收集器不会删除闭包引用的对象,只要仍然引用闭包.