15

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

例如,在 Ruby 中,“闭包实际上会延长它需要的所有变量的生命周期。它不会复制它们,但会保留对它们的引用,并且变量本身将不符合垃圾回收的条件(如果语言有垃圾收集)而关闭时“。[斯科尔金]

4

2 回答 2

26

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

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

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

l.map(new SomeFreshName())

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

如果你关闭了一些局部变量,或者等效的函数参数,它们必须作为构造函数参数传递给闭包实现类:

对于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))

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

注意:如果 s 是类的字段而不是方法的局部字段,那么s + i实际上是this.s + i,并且this会被传递给匿名类。

垃圾收集器没有什么特别的(同样,jvm 不知道闭包),简单地说,由于闭包对象具有对 s 的引用,因此 s 将至少与闭包对象一样长。

请注意,在带有匿名类的 java 语言中也会发生完全相同的事情。当匿名类使用封闭方法的局部变量时,这些局部变量会被默默地添加为匿名类的字段,并在构造函数中传递。

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

事实上,通过这个实现,一旦创建了闭包,它就有自己的变量副本,它关闭了其他变量。如果它修改它们,这些修改将不会反映在方法中。如果它们在闭包中被修改,这将不会反映在方法中。

假设你写

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

之前描述的实现将是

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") 

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

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

class Ref[A](var content: A)

代码首先被替换为

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")

这当然只针对恰好被闭包占用的 var,而不是针对每个 var。这样做,var 已被 val 替换,实际变量值已移入堆中。现在,关闭可以像往常一样完成,并且可以正常工作

class SomeFreshName(iRef: Ref[Int]) ...
于 2012-07-25T21:26:57.550 回答
13

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 */

我无法评论垃圾收集,但我假设 JVM 的垃圾收集器不会删除由闭包引用的对象,只要该闭包仍然被引用。

于 2012-07-25T20:12:46.530 回答