2012-07-25 69 views
13

scala是否通過複製或引用來維護變量的值?Scala在定義閉包時如何保持變量的值?

例如,在Ruby中,閉包實際上會延長它需要的所有變量的生命週期,它不會複製它們,但會保留對它們的引用,並且變量本身將不符合垃圾回收的條件如果語言有垃圾回收),而關閉是「。 [SKORKIN]

回答

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

我無法對垃圾回收評論,但我認爲JVM的垃圾收集器將不會刪除通過封閉引用,只要關閉仍在引用的對象。

20

jvm沒有關閉,它只有對象。 scala編譯器生成匿名類,用於實現代碼中每個閉包的每次出現的相應Function trait(取決於簽名的參數和結果類型)。

例如,如果由於某種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的,不存在自由變量,只有參數我和常量。

如果關閉了一些當地的丘壑,或等效的函數的參數,他們將不得不爲構造函數的參數於封閉,實現類要傳遞:

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 + ithis將被傳遞給匿名類。

垃圾收集器沒有什麼特別的地方(同樣,jvm不知道閉包),簡單地說,因爲閉包對象有一個對s的引用,s將至少與閉包對象一樣長。

請注意,使用匿名類的Java語言中發生的情況完全相同。當一個匿名類使用封閉方法的局部變量時,這些局部變量會默默地添加爲匿名類的字段,並在構造函數處傳遞。

在java中,只有當本地是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]) ... 
+2

提及'Ref'的+1。這是我希望很少有人提及的技術細節。 – 2012-07-25 22:19:38

+0

好的 - 我確實想念你的觀點,對此感到遺憾。 – sourcedelica 2012-07-29 01:51:01