學習Scala的閉包
到本章這里,所有函數(shù)文本的例子僅參考了傳入的參數(shù)。例如,(x: Int) => x > 0里,函數(shù)體用到的***變量,x > 0,是x,被定義為函數(shù)參數(shù)。然而也可以參考定義在其它地方的變量:
- (x: Int) => x + more // more是多少?
51CTO編輯推薦:Scala編程語言專題
函數(shù)把“more”加入?yún)⒖?,但什么是more呢?從這個函數(shù)的視點來看,more是個自由變量:free variable,因為函數(shù)文本自身沒有給出其含義。相對的,x變量是一個綁定變量:bound variable,因為它在函數(shù)的上下文中有明確意義:被定義為函數(shù)的***參數(shù),一個Int。如果你嘗試獨立使用這個函數(shù)文本,范圍內(nèi)沒有任何more的定義,編譯器會報錯說:
另一方面,只要有一個叫做more的什么東西同樣的函數(shù)文本將工作正常:
- scala> (x: Int) => x + more
- < console>:5: error: not found: value more
- (x: Int) => x + more
- ˆ
依照這個函數(shù)文本在運行時創(chuàng)建的函數(shù)值(對象)被稱為閉包:closure。名稱源自于通過“捕獲”自由變量的綁定對函數(shù)文本執(zhí)行的“關(guān)閉”行動。不帶自由變量的函數(shù)文本,如(x: Int) => x + 1,被稱為封閉術(shù)語:closed term,這里術(shù)語:term指的是一小部分源代碼。因此依照這個函數(shù)文本在運行時創(chuàng)建的函數(shù)值嚴格意義上來講就不是閉包,因為(x: Int) => x + 1在編寫的時候就已經(jīng)封閉了。但任何帶有自由變量的函數(shù)文本,如(x: Int) => x + more,都是開放術(shù)語:open term。因此,任何依照(x: Int) => x + more在運行期創(chuàng)建的函數(shù)值將必須捕獲它的自由變量,more,的綁定。由于函數(shù)值是關(guān)閉這個開放術(shù)語(x: Int) => x + more的行動的最終產(chǎn)物,得到的函數(shù)值將包含一個指向捕獲的more變量的參考,因此被稱為閉包。
- scala> var more = 1
- more: Int = 1
- scala> val addMore = (x: Int) => x + more
- addMore: (Int) => Int = < function>
- scala> addMore(10)
- res19: Int = 11
這個例子帶來一個問題:如果more在閉包創(chuàng)建之后被改變了會發(fā)生什么事?Scala里,答案是閉包看到了這個變化。如下:
直覺上,Scala的閉包捕獲了變量本身,而不是變量指向的值。相對的,Java的內(nèi)部類根本不允許你訪問外圍范圍內(nèi)可以改變的變量,因此到底是捕獲了變量還是捕獲了它當前具有的值就沒有差別了。就像前面演示的例子,依照(x: Int) => x + more創(chuàng)建的閉包看到了閉包之外做出的對more的變化。反過來也同樣。閉包對捕獲變量作出的改變在閉包之外也可見。下面是一個例子:
- scala> more = 9999
- more: Int = 9999
- scala> addMore(10)
- res21: Int = 10009
例子用了一個循環(huán)的方式計算List的累加和。變量sum處于函數(shù)文本sum += _的外圍,函數(shù)文本把數(shù)累加到sum上。盡管這是一個在運行期改變sum的閉包,作為結(jié)果的累加值,-11,仍然在閉包之外可見。
- scala> val someNumbers = List(-11, -10, -5, 0, 5, 10)
- someNumbers: List[Int] = List(-11, -10, -5, 0, 5, 10)
- scala> var sum = 0
- sum: Int = 0
- scala> someNumbers.foreach(sum += _)
- scala> sum
- res23: Int = -11
如果閉包訪問了某些在程序運行時有若干不同備份的變量會怎樣?例如,如果閉包使用了某個函數(shù)的本地變量,并且函數(shù)被調(diào)用很多次會怎樣?每一次訪問使用的是變量的哪個實例?
僅有一個答案與語言余下的部分共存:使用的實例是那個在閉包被創(chuàng)建的時候活躍的。例如,以下是創(chuàng)建和返回“遞增”閉包的函數(shù):
每次函數(shù)被調(diào)用時都會創(chuàng)建一個新閉包。每個閉包都會訪問閉包創(chuàng)建時活躍的more變量。
- def makeIncreaser(more: Int) = (x: Int) => x + more
調(diào)用makeIncreaser(1)時,捕獲值1當作more的綁定的閉包被創(chuàng)建并返回。相似地,調(diào)用makeIncreaser(9999),捕獲值9999當作more的閉包被返回。當你把這些閉包應(yīng)用到參數(shù)上(本例中,只有一個參數(shù),x,必須被傳入),回來的結(jié)果依賴于閉包被創(chuàng)建時more是如何定義的:
- scala> val inc1 = makeIncreaser(1)
- inc1: (Int) => Int = < function>
- scala> val inc9999 = makeIncreaser(9999)
- inc9999: (Int) => Int = < function>
- scala> inc1(10)
- res24: Int = 11
- scala> inc9999(10)
- res25: Int = 10009
盡管本例中more是一個已經(jīng)返回的方法調(diào)用的參數(shù)也沒有區(qū)別。Scala編譯器在這種情況下重新安排了它以使得捕獲的參數(shù)繼續(xù)存在于堆中,而不是堆棧中,因此可以保留在創(chuàng)建它的方法調(diào)用之外。這種重新安排的工作都是自動關(guān)照的,因此你不需要操心。請任意捕獲你想要的變量:val,var,或參數(shù)。
【相關(guān)閱讀】