閉包簡介
Groovy中的閉包可以理解爲是包裝成對象的一段代碼,你可以像對待一個普通對象一樣看待它,也可以將它看作一個方法,它可以接受參數也有返回值。Groovy的語法很簡潔,導致閉包的一些邏輯比較難以理解。使用閉包有兩個比較突出的優勢:一是可以方便的操作集合;二是對資源的使用更安全,比如操作文件的時候不用再去但因有沒有關閉文件等問題。
聲明閉包
簡單聲明
閉包的格式主要是:在方法調用之後,使用大括號將代碼片段包起來,括號裏面包含參數和閉包主體(代碼邏輯),他們之間以箭頭分隔。當閉包的參數只有一個的時候,參數可以用關鍵字it代替,且不需要聲明,如下兩種方式聲明閉包是等價的:
def log = ''
(1..10).each { counter ->
log = log + counter
}
assert log == '12345678910'
def log1 = ''
(1..10).each {//it的聲明可以省略
log1 = log1 + it
}
assert log1 == '12345678910'
將閉包賦值給變量
另一種聲明閉包的方法就是將其直接賦值給一個變量,如下所示,將閉包賦值給了變量printer:
def printer = { line -> println line }
除了這樣直接賦值給變量,也可以將閉包賦值給一個方法的返回值:
def getPrinter() {
return { line -> println line }
}
將方法引用爲閉包
可以將一個方法的代碼邏輯直接因爲爲一個閉包,然後再對其使用一些閉包的方法,將方法應用爲閉包需要使用操作符".&",如下所示:
- SizeFilter這個類定義了一個limit的全局變量,包含了方法sizeUpTo。
class SizeFilter {
Integer limit
def sizeUpTo(String value) {
return value.size() <= limit//返回size小於等於limit的value
}
}
- class Filter的filter6和filter5是class SizeFilter的兩個實例。
- 定義閉包sizeUpTo6,是將SizeFilter裏的方法sizeUpTo直接引用爲閉包。
- 定義一個list words
- wordSizeUpTo6是對閉包sizeUpTo6的操作,知道word list中size下雨等於6的word
- wordSizeUpTo5中,沒有定一個閉包,而是直接將filter5.&sizeUpTo直接傳入find方法中進行操作,這也是可以的,因爲filter5.&sizeUpTo也是一個閉包。
class Filter {
SizeFilter filter6 = new SizeFilter(limit: 6)
SizeFilter filter5 = new SizeFilter(limit: 5)
Closure sizeUpTo6 = filter6.&sizeUpTo//引用方法sizeUpTo作爲閉包
def words = ['long string', 'medium', 'short', 'tiny']
@Test
void wordSizeUpTo6() {
assert words.find(sizeUpTo6) == "medium"//找到第一個word size小於或等於6的
assert words.findAll(sizeUpTo6) == ['medium', 'short', 'tiny']//找到所有size小於或等於6的word
}
@Test
void wordSizeUpTo5() {
assert words.find(filter5.&sizeUpTo) == "short"//找到第一個size小於或等於5的word
assert words.findAll(filter5.&sizeUpTo) == ['short', 'tiny']//找到所有size小於或等於5的word
}
}
使用閉包
調用閉包
調用閉包我們可以將其看作是調用一個方法,傳入其需要參數即可。如下所示,x.closure()或x.closure.call()都是可以的:
def adder = { x, y -> return x + y }//聲明閉包
@Test
void callAdder() {
assert adder(3, 7) == 10//調用閉包,傳入參數(x,y)
assert adder.call(3, 9) == 12
}
下面給一個更復雜的例子,在方法裏面來調用閉包。
def benchmark(int repeat, Closure worker) {//定義閉包worker作爲方法參數傳入
def start = System.nanoTime()//記錄開始時間
repeat.times {//times表示多次(repeat)執行閉包
worker(it)//it表示執行次數,從0開始,這裏的it是可以免聲明的
}
def stop = System.nanoTime()//記錄結束時間
return stop - start//返回執行時間
}
@Test
void callBenchmark() {//調用benchMark方法
def slow = benchmark(10000) { it / 2 }//repeat=10000,worker={it / 2}
def fast = benchmark(10000) { it.intdiv(2) }//repeat=10000,worker={it.intdiv(2)}
assert fast * 2 < slow
}
閉包的其他使用方法
我們常常使用閉包的就是它的聲明和調用,但是它還有一些其他的使用場景。
處理參數個數和類型
上面使用閉包的例子都是傳入一個參數,其實閉包也可以傳入多個參數,比如和map操作的時候,可以傳入key和value,然後閉包根據你傳入的參數個數、類型來調整其行爲。那我們也可以使用getMaximumNumberOfParameters和getParameterTypes方法來檢索預期的參數個數和類型。如下所示:
def numParams(Closure closure) {
closure.getMaximumNumberOfParameters()
}
@Test
void callNumParams() {
def numOfParams = numParams { one -> }
assert numOfParams == 1
assert numParams { one, two -> } == 2
}
def paramTypes(Closure closure) {
closure.getParameterTypes()
}
@Test
void callParamTypes() {
assert paramTypes { String s -> } == [String]
assert paramTypes { Number n, Date d, String s -> } == [Number, Date, String]
}
如何使用Curry?
Curry是以人名(Haskell Brooks Curry )命名的技術,主要思想是:通過固定一些入餐的值將有多個參數的方法轉換成只有少數幾個甚至一個參數的方法。
一個簡單的curry方法的示例如下:
def mult = { x, y -> return x * y }//聲明一個閉包
def twoTimes = mult.curry(2)//一個新的閉包,賦給了第一個參數(默認)值爲2
assert twoTimes(5) == 10//一個新的閉包,只需要一個參數(第二個參數)y。
給指定的參數賦值,以上示例使用的默認給左邊第一個參數賦值,也有其他方法給指定的參數賦值,比如rcurry(給最右邊的第一個參數賦值),ncurry(給第n個參數賦值),lcurry(明確指定賦值給左邊第一個參數)。如下所示:
def mult = { x, y, z -> return x * y + z }
def twoTimes = mult.rcurry(2)//一個新的閉包,賦給了z值爲2
assert twoTimes(3, 4) == 14
def mult = { x, y, z, m, n -> return x * y + z / m + n }
def twoTimes = mult.ncurry(0, 2)//一個新的閉包,賦給了x值爲2(n從0到4)
assert twoTimes(3, 4, 2, 6) == 14
多個函數組合
可以將多個函數組合起來,合併成一個函數。如下示例:
def add = { a -> a + 5 }
def mult = { a -> a * 4 }
def rightShift = add >> mult//先+再*
def leftShift = add << mult//先*再+
assert rightShift(3) == 32
assert leftShift(3) == 17
閉包返回值
結束返回
結束返回的意思就是閉包邏輯執行到最後一句的時候返回結果,return關鍵字可以省略,如下:
[1, 2, 3].collect { it * 2 }
[1, 2, 3].collect { return it * 2 }
結束前返回
可以在閉包運算結束前就返回,如下所示:
[1, 2, 3].collect {
if (it % 2 == 0) return it * 2//if部分返回值
return it//else部分返回值
}