A closure is a function with variables bound to a context or environment in which it executes.html
閉包和元編程是Groovy語言的兩大精髓。Groovy的閉包大大簡化了容器的遍歷,提高了代碼的可擴展性,使代碼更加簡潔優雅。閉包在Groovy編程中幾乎無處不在。算法
閉包就是一個閉合代碼塊,能夠引用傳入的變量。在 「Python使用閉包結合配置自動生成函數」 一文中,講解了閉包的基本概念及如何使用閉包批量生產函數。本文談談Groovy的閉包及應用。
編程
閉包在Groovy 的類型是 groovy.lang.Closure , 以下代碼建立了一個使用 closure 來處理 Range [1,2,...,num] 的函數:設計模式
def static funcWithClosure(int num, final Closure closure) { (1..num).collect { closure(it) } }
使用該函數的代碼以下:閉包
println funcWithClosure(5, {x -> x*x})
若是閉包是最後一個參數,還能夠寫成:框架
println funcWithClosure(5) { x -> x * 2 }
有童鞋可能疑惑:閉包的形式很像函數,它與函數有什麼區別呢?函數
咱們知道函數執行完成後,其內部變量會所有銷燬,但閉包不會。閉包引用的外部變量會一直保存。閉包引用的外部變量具備「累積效應」,而函數沒有。看下面一段代碼:設計
def static add(num) { def sum = 0 sum += num return sum } def static addByClosure(init) { def addInner = { inc -> init += inc init } return addInner } println "one call: ${add(5)}" // one call: 5 println "two call: ${add(5)}" // two call: 5 def addClosure = addByClosure(0) println "one call: ${addClosure(5)}" // one call: 5 println "two call: ${addClosure(5)}" // two call: 10
第一個函數沒有什麼特別,進進出出,每次運行獲得相同結果。 第二個函數,返回了一個閉包,這個閉包保存了傳入的初始值,而且這個閉包可以將初始值加上後續傳入給它的參數。劃重點: 這裏的初始值 init 是函數傳入的參數,當這個參數被閉包引用後,它在函數第一次執行完成後值並無被銷燬,而是保存下來。
code
在 「函數柯里化(Currying)示例」 一文中講述了函數柯里化的概念及Scala示例。Groovy 也提供了 curry 函數來支持 Curry.htm
以下所示,計算 sumPower(num, p) = 1^p + 2^p + ... + num^p 。
// sum(n, m) = 1^m + 2^m + ... + n^m def sumPower = { power, num -> def sum = 0 1.upto(num) { sum += Math.pow(it, power) } sum } def sumPower_2 = sumPower.curry(2) println "1^2 + 2^2 + 3^2 = ${sumPower_2(3)}" println "1^2 + 2^2 + 3^2 + 4^2 = ${sumPower_2(4)}" def sumPower_3 = sumPower.curry(3) println "1^3 + 2^3 + 3^3 = ${sumPower_3(3)}" println "1^3 + 2^3 + 3^3 + 4^3 = ${sumPower_3(4)}"
sumPower.curry(2) 先賦值 power = 2 帶入閉包塊,獲得一個閉包:
def sumPower_2Explict = { num -> def sum = 0 1.upto(num) { sum += Math.pow(it, 2) } sum }
再分別調用 sumPower_2Explict(3) = 14.0 , sumPower_2Explict(4) = 30.0
柯里化使得閉包的威力更增強大了。 它是一個強大的函數工廠,能夠批量生產大量有用的函數。
閉包能夠很容易地實現如下功能:
以下代碼所示,分別建立了一個Map, List 和 Range, 而後使用 each 方法遍歷。一個閉合代碼塊,加上一個遍歷變量,清晰簡單。注意到,若是是一個單循環遍歷,能夠直接用 it 表示;若是是 Map 遍歷,使用 key, value 二元組便可。
class GroovyBasics { static void main(args) { def map = ["me":["name": 'qin', "age": 28], "lover":["name": 'ni', "age": 25]] map.each { key, value -> println(key+"="+value) } def alist = [1,3,5,7,9] alist.each { println(it) } (1..10).each { println(it) } def persons = [new Person(["name": 'qin', "age": 28]), new Person(["name": 'ni', "age": 25])] println persons.collect { it.name } println persons.find { it.age >=28 }.name } }
再看一段代碼:
(1..10).groupBy { it % 3 == 0 } .each { key, value -> println(key.toString()+"="+value) }
將 [1,10]之間的數按照是否被3除盡分組獲得以下結果,使用鏈式調用鏈接的兩個閉包實現,很是簡明。
false=[1, 2, 4, 5, 7, 8, 10]
true=[3, 6, 9]
模板方法模式將算法的可變與不可變部分分離出來。 一般遵循以下模式: doCommon1 -> doDiff1 -> ... DoDiff2 -> ... -> DoCommon2 。 Java 實現模板方法模式,一般須要先定義一個抽象類,在抽象類中定義好算法的基本流程,而後定義算法裏那些可變的部分,由子類去實現。可參閱:「設計模式之模板方法模式:實現可擴展性設計(Java示例)」 。
使用閉包能夠很是輕鬆地實現模板方法模式,只要將可變部分定義成 閉包便可。
def static templateMethod(list, common1, diff1, diff2, common2) { common1 list diff1 list diff2 list common2 list } def common1 = { list -> list.sort() } def common2 = { println it } def diff1 = { list -> list.unique() } def diff2 = { list -> list } templateMethod([2,6,1,9,8,2,4,5], common1, diff1, diff2, common2)
可複用與可擴展性的前提是,將規則、流程中的可變與不可變分離出來,不可變表明着可複用部分,可變表明着可擴展的部分。因爲閉包可以很容易地將可變與不可變分離,所以也頗有益於實現代碼的可複用與可擴展。
閉包和元編程是Groovy語言的兩大精髓。本文講解了Groovy閉包的定義、與函數的區別、柯里化及在遍歷容器、實現模板方法模式等應用。使用閉包可提高代碼的可複用和可擴展性,使代碼更加簡潔優雅,對於提高編程能力很是有益處。