Scala中的閉包

2021-09-11 09:39:41 字數 2707 閱讀 7525

在scala中,函式引入傳入的引數是再正常不過的事情了,比如(x: int) => x > 0中,唯一在函式體x > 0中用到的變數是x,即這個函式的唯一引數。

除此之外,scala還支援引用其他地方定義的變數:(x: int) => x + more,這個函式將more也作為入參,不過這個引數是**來的?從這個函式的角度來看,more是乙個自由變數,因為函式字面量本身並沒有給more賦予任何含義。相反,x是乙個繫結變數,因為它在該函式的上下文裡有明確的定義:它被定義為該函式的唯一引數。如果單獨使用這個函式字面量,而沒有在任何處於作用域內的地方定義more,編譯器將報錯:

scala> (x: int) => x + more

:12: error: not found: value more

(x: int) => x + more

複製**

另一方面,只要能找到名為more的變數,同樣的函式字面量就能正常工作:

scala> var more = 1

more: int = 1

scala> val addmore = (x: int) => x + more

addmore: int => int = $$lambda

$1104/583744857@33e4b9c4

scala> addmore(10)

res0: int = 11

複製**

執行時從這個函式字面量建立出來的函式值(物件)被稱為閉包。該名稱源於「捕獲」其自由變數從而「閉合」該函式字面量的動作。沒有自由變數的函式字面量,比如(x: int) => x + 1,稱為閉合語(這裡的語指的是一段源**)。因此,執行時從這個函式字面量建立出來的函式值嚴格來說並不是乙個閉包,因為(x: int) => x + 1按照目前這個寫法已經是閉合的了。而執行時從任何帶有自由變數的函式字面量,比如(x: int) => x + more建立的函式,按照定義,要求捕獲到它的自由變數more的繫結。相應的函式值結果(包含指向**獲的more變數的引用)就被稱為閉包,因為函式值是通過閉合這個開放語的動作產生的。

scala> more = 9999

more: int = 9999

scala> addmore(10)

res1: int = 10009

複製**

很符合直覺的是,scala的閉包捕獲的是變數本身,而不是變數引用的值。正如前面示例所展示的,為(x: int) => x + more建立的閉包能夠看到閉包外對more的修改。反過來也是成立的:閉包對捕獲到的變數的修改也能在閉包外被看到。參考下面的例子:

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

res3: int = -11

複製**

這個例子通過遍歷的方式來對list中的數字求和。sum這個變數位於函式字面量sum += _的外圍作用域,這個函式將數字加給sum。雖然執行時是這個閉包對sum進行的修改,最終的結果-11仍然能被閉包外部看到。

那麼,如果乙個閉包訪問了某個隨著程式執行會產生多個副本的變數會如何呢?例如,如果乙個閉包使用了某個函式的區域性變數,而這個函式又被呼叫了多次,會怎麼樣?閉包每次訪問到的是這個變數的哪乙個例項呢?

def makeincreaser(more: int) = (x: int) => x + more

複製**

該函式每呼叫一次,就會建立乙個新的閉包。每個閉包都會訪問那個在它建立時活躍的變數more

scala> val inc1 = makeincreaser(1)

inc1: int => int = $$lambda

$1269/1504482477@1179731c

scala> val inc9999 = makeincreaser(9999)

inc9999: int => int = $$lambda

$1269/1504482477@2dba6013

複製**

當呼叫makeincreaser(1)時,乙個捕獲了more的繫結值為1的閉包就被建立並返回。同理,當呼叫makeincreaser(9999)時,返回的是乙個捕獲了more的繫結值9999的閉包。當你將這些閉包應用到入參時,其返回結果取決於閉包建立時more的定義

scala> inc1(10)

res4: int = 11

scala> inc9999(10)

res5: int = 10009

複製**

這裡,more是某次方法呼叫的入參,而方法已經返回了,不過這並沒有影響。scala編譯器會重新組織和安排,讓**獲的引數在堆上繼續存活。這樣的安排都是由編譯器自動完成的,使用者並不需要關心。

scala中閉包的使用

閉包的實質就是 與用到的非區域性變數的混合,即 閉包 用到的非區域性變數 例項1 匿名函式中引入閉包 val multiplier i int i factor 在 multiplier 中有兩個變數 i 和 factor。其中的乙個 i 是函式的形式引數,在 multiplier 函式被呼叫時,i...

Scala閉包詳解

閉包是乙個函式,返回值依賴於宣告在函式外部的乙個或多個變數。閉包通常來講可以簡單的認為是可以訪問乙個函式裡面區域性變數的另外乙個函式。如下面這段匿名的函式 val multiplier i int i 10函式體內有乙個變數 i,它作為函式的乙個引數。如下面的另一段 val multiplier i...

閉包函式 scala

閉包函式 返回結果 依賴外部引數 正常函式 val mult x int 閉包函式 var factor 5 依賴引數 閉包函式mult1 val mult1 x int x factor println 閉包函式 mult1 在factor factor 時,結果為 mult1 10 factor...