Groovy 閉包

本文介紹了Groovy閉包的有關(guān)內(nèi)容。閉包可以說(shuō)是Groovy中最重要的功能了沐绒。如果沒(méi)有閉包俩莽,那么Groovy除了語(yǔ)法比Java簡(jiǎn)單點(diǎn)之外,沒(méi)有任何優(yōu)勢(shì)乔遮。但是閉包扮超,讓Groovy這門語(yǔ)言具有了強(qiáng)大的功能。如果你希望構(gòu)建自己的領(lǐng)域描述語(yǔ)言(DSL)蹋肮,Groovy是一個(gè)很好的選擇出刷。Gradle就是一個(gè)非常成功的例子。

本文參考自Groovy 文檔 閉包坯辩,為了方便馁龟,大部分代碼直接引用了Groovy文檔。

定義閉包

閉包在花括號(hào)內(nèi)定義漆魔。我們可以看到Groovy閉包和Java的lambda表達(dá)式差不多坷檩,但是學(xué)習(xí)之后就會(huì)發(fā)現(xiàn),Groovy的閉包功能更加強(qiáng)大改抡。

{ [closureParameters -> ] statements }

閉包的參數(shù)列表是可選的矢炼,參數(shù)的類型也是可選的。如果我們不指定參數(shù)的類型阿纤,會(huì)由編譯器自動(dòng)推斷句灌。如果閉包只有一個(gè)參數(shù),這個(gè)參數(shù)可以省略欠拾,我們可以直接使用it來(lái)訪問(wèn)該參數(shù)胰锌。以下是Groovy文檔的例子。下面這些都是合法的閉包藐窄。

{ item++ }                                          

{ -> item++ }                                       

{ println it }                                      

{ it -> println it }                                

{ name -> println name }                            

{ String x, int y ->                                
    println "hey ${x} the value is ${y}"
}

{ reader ->                                         
    def line = reader.readLine()
    line.trim()
}

需要注意閉包的隱式參數(shù)it總是存在资昧,即使我們省去->操作符。除非我們顯式在閉包的參數(shù)列表上什么都不指定枷邪。

def magicNumber = { -> 42 }  //顯示指定閉包沒(méi)有參數(shù)

閉包的參數(shù)還可以使用可變參數(shù)榛搔。

def concat1 = { String... args -> args.join('') }  //可變參數(shù)诺凡,個(gè)數(shù)不定

使用閉包

我們可以將閉包賦給變量东揣,然后可以將變量作為函數(shù)來(lái)調(diào)用,或者調(diào)用閉包的call方法也可以調(diào)用閉包腹泌。閉包實(shí)際上是groovy.lang.Closure類型嘶卧,泛型版本的泛型表示閉包的返回類型。

def fun = { println("$it") }
fun(1234)

Closure date = { println(LocalDate.now()) }
date.call()

Closure<LocalTime> time = { LocalTime.now() }
println("now is ${time()}")

委托策略

閉包的相關(guān)對(duì)象

Groovy的閉包比Java的Lambda表達(dá)式功能更強(qiáng)大凉袱。原因就是Groovy閉包可以修改委托對(duì)象和委托策略芥吟。這樣Groovy就可以實(shí)現(xiàn)非常優(yōu)美的領(lǐng)域描述語(yǔ)言(DSL)了侦铜。Gradle就是一個(gè)鮮明的例子。

Groovy閉包有3個(gè)相關(guān)對(duì)象钟鸵。

  • this 即閉包定義所在的類钉稍。
  • owner 即閉包定義所在的對(duì)象或閉包。
  • delegate 即閉包中引用的第三方對(duì)象棺耍。

前面兩個(gè)對(duì)象都很好理解贡未。delegate對(duì)象需要我們手動(dòng)指定。

class Person {
    String name
}
def p = new Person(name:'Igor')
def cl = { name.toUpperCase() }                 
cl.delegate = p                                 
assert cl() == 'IGOR'   

相應(yīng)的Groovy有幾種屬性解析策略蒙袍,幫助我們解析閉包中遇到的屬性和方法引用俊卤。我們可以使用閉包的resolveStrategy屬性修改策略。

  • Closure.OWNER_FIRST害幅,默認(rèn)策略消恍,首先從owner上尋找屬性或方法,找不到則在delegate上尋找以现。
  • Closure.DELEGATE_FIRST狠怨,和上面相反。
  • Closure.OWNER_ONLY邑遏,只在owner上尋找取董,delegate被忽略。
  • Closure.DELEGATE_ONLY无宿,和上面相反茵汰。
  • Closure.TO_SELF,高級(jí)選項(xiàng)孽鸡,讓開(kāi)發(fā)者自定義策略蹂午。

Groovy文檔有詳細(xì)的代碼例子,說(shuō)明了這幾種策略的行為彬碱。這里就不再細(xì)述了豆胸。

函數(shù)式編程

GString的閉包

先看下面的例子。我們使用了GString的內(nèi)插字符串巷疼,將一個(gè)變量插入到字符串中晚胡。這工作非常正常。

def x = 1
def gs = "x = ${x}"
assert gs == 'x = 1'

如果我們現(xiàn)在改變了變量的值嚼沿,然后再看看結(jié)果估盘。結(jié)果可能出乎你的意料,輸出仍然是x = 1骡尽。原因有兩個(gè):一是GString只能延遲計(jì)算值的toString表示形式遣妥;二是表達(dá)式${x}的計(jì)算發(fā)生在GString創(chuàng)建的時(shí)候,然后就不會(huì)計(jì)算了攀细。

x = 2
assert !gs == 'x = 2'

如果我們希望字符串的結(jié)果隨著變量的改變而改變箫踩,需要將${x}聲明為閉包爱态。這樣,GString的行為就和我們想的一樣了境钟。

def x = 1
def gs = "x = ${-> x}"
assert gs == 'x = 1'

x = 2
assert gs == 'x = 2'

函數(shù)范例

柯里化

首先來(lái)看看閉包的柯里化锦担,也就是將多個(gè)參數(shù)的函數(shù)轉(zhuǎn)變?yōu)橹唤邮芤粋€(gè)參數(shù)的函數(shù)。我們?cè)陂]包上調(diào)用ncurry方法來(lái)實(shí)現(xiàn)慨削,它會(huì)固定指定索引的參數(shù)吆豹。另外還有curryrcurry方法,用于固定最左邊和最右邊的參數(shù)理盆。

def volume = { double l, double w, double h -> l*w*h }      
def fixedWidthVolume = volume.ncurry(1, 2d)     //將索引為1的參數(shù)固定為2d            
assert volume(3d, 2d, 4d) == fixedWidthVolume(3d, 4d)       
def fixedWidthAndHeight = volume.ncurry(1, 2d, 4d)        //將寬和高固定  
assert volume(3d, 2d, 4d) == fixedWidthAndHeight(3d) 

緩存

我們還可以緩存閉包的結(jié)果痘煤。Groovy文檔用了斐波那契數(shù)列做例子。這個(gè)實(shí)現(xiàn)的缺點(diǎn)就是重復(fù)計(jì)算次數(shù)太多了猿规。Groovy文檔給出的評(píng)價(jià)是naive!

def fib
fib = { long n -> n<2?n:fib(n-1)+fib(n-2) }
assert fib(15) == 610 // 太慢了

我們可以在閉包上調(diào)用memoize()方法來(lái)生成一個(gè)新閉包衷快,該閉包具有緩存執(zhí)行結(jié)果的行為。緩存使用近期最少使用算法(LRU)姨俩。

fib = { long n -> n<2?n:fib(n-1)+fib(n-2) }.memoize()
assert fib(25) == 75025 //很快

緩存會(huì)使用閉包的實(shí)際參數(shù)的值蘸拔,因此我們?cè)谑褂梅腔绢愋蛥?shù)的時(shí)候必須格外小心,避免構(gòu)造大量對(duì)象或者進(jìn)行無(wú)謂的裝箱环葵、拆箱操作调窍。

還有幾個(gè)方法提供了不同的緩存行為。

  • memoizeAtMost 生成一個(gè)最多緩存N個(gè)對(duì)象的新閉包张遭。
  • memoizeAtLeast 生成一個(gè)最少緩存N個(gè)對(duì)象的新閉包邓萨。
  • memoizeBetween 生成一個(gè)新閉包,緩存?zhèn)€數(shù)在給定的兩者之間菊卷。

復(fù)合

閉包還可以復(fù)合缔恳。學(xué)過(guò)高數(shù)的話應(yīng)該很好理解,這就是多個(gè)函數(shù)的復(fù)合(f(g(x))和g(f(x))的區(qū)別)洁闰。

def plus2  = { it + 2 }
def times3 = { it * 3 }

def times3plus2 = plus2 << times3
assert times3plus2(3) == 11
assert times3plus2(4) == plus2(times3(4))

def plus2times3 = times3 << plus2
assert plus2times3(3) == 15
assert plus2times3(5) == times3(plus2(5))

// reverse composition
assert times3plus2(3) == (times3 >> plus2)(3)

尾遞歸(Trampoline)

文檔原文是Trampoline歉甚,可惜我沒(méi)明白是什么意思。不過(guò)這里的意思就是尾遞歸扑眉,所以我就這么叫了纸泄。遞歸函數(shù)在調(diào)用層數(shù)過(guò)多的時(shí)候,有可能會(huì)用盡椦兀空間聘裁,導(dǎo)致拋出StackOverflowException。我們可以使用閉包的尾遞歸來(lái)避免爆棧耸弄。

普通的遞歸函數(shù)咧虎,需要在自身中調(diào)用自身,因此必須有多層函數(shù)調(diào)用棧计呈。如果遞歸函數(shù)的最后一個(gè)語(yǔ)句是遞歸調(diào)用本身砰诵,那么就有可能執(zhí)行尾遞歸優(yōu)化,將多層函數(shù)調(diào)用轉(zhuǎn)化為連續(xù)的函數(shù)調(diào)用捌显。這樣函數(shù)調(diào)用棧只有一層茁彭,就不會(huì)發(fā)生爆棧異常了。

尾遞歸需要調(diào)用閉包的trampoline()方法扶歪,它會(huì)返回一個(gè)TrampolineClosure理肺,具有尾遞歸特性。注意這里我們需要將外層閉包和遞歸閉包都調(diào)用trampoline()方法善镰,才能正確的使用尾遞歸特性妹萨。然后我們計(jì)算一個(gè)很大的數(shù)字,就不會(huì)出現(xiàn)爆棧錯(cuò)誤了炫欺。

def factorial
factorial = { int n, def accu = 1G ->
    if (n < 2) return accu
    factorial.trampoline(n - 1, n * accu)
}
factorial = factorial.trampoline()

assert factorial(1)    == 1
assert factorial(3)    == 1 * 2 * 3
assert factorial(1000) // == 402387260.. plus another 2560 digits
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末乎完,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子品洛,更是在濱河造成了極大的恐慌树姨,老刑警劉巖抚岗,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件傻寂,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡屋谭,警方通過(guò)查閱死者的電腦和手機(jī)辅斟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門转晰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人士飒,你說(shuō)我怎么就攤上這事挽霉。” “怎么了变汪?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵侠坎,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我裙盾,道長(zhǎng)实胸,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任番官,我火速辦了婚禮庐完,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘徘熔。我一直安慰自己门躯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布酷师。 她就那樣靜靜地躺著讶凉,像睡著了一般染乌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上懂讯,一...
    開(kāi)封第一講書(shū)人閱讀 51,365評(píng)論 1 302
  • 那天荷憋,我揣著相機(jī)與錄音,去河邊找鬼褐望。 笑死勒庄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瘫里。 我是一名探鬼主播实蔽,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼谨读!你這毒婦竟也來(lái)了局装?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤漆腌,失蹤者是張志新(化名)和其女友劉穎贼邓,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體闷尿,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡塑径,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了填具。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片统舀。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖劳景,靈堂內(nèi)的尸體忽然破棺而出誉简,到底是詐尸還是另有隱情,我是刑警寧澤盟广,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布闷串,位于F島的核電站,受9級(jí)特大地震影響筋量,放射性物質(zhì)發(fā)生泄漏烹吵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一桨武、第九天 我趴在偏房一處隱蔽的房頂上張望肋拔。 院中可真熱鬧,春花似錦呀酸、人聲如沸凉蜂。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)窿吩。三九已至茎杂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間爆存,已是汗流浹背蛉顽。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工蝗砾, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留先较,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓悼粮,卻偏偏與公主長(zhǎng)得像闲勺,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子扣猫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容