簡介:Groovy中的閉包就是去掉冗長無用代碼的短小的匿名方法,閉包從函數式編程的Lambda表達式(指定了一個函數的參數與映射)派生而來。
一、閉包的便利性
groovy的閉包特性,大大簡化了代碼,而且其可以輔助輕量級、可復用的代碼塊。例子:
偶數相加:
/**
*傳統代碼一
*@paramn
*@return
*/
def sum(n) {
total =0
for(inti =2; i <= n; i +=2) {
total += i
}
total
}
偶數相乘
/**
*傳統代碼二
*@paramn
*@return
*/
def product(n) {
prod =1
for(inti =2; i <= n; i +=2) {
prod *= i
}
prod
}
偶數次冪
/**
*傳統代碼三
*@paramn
*@return
*/
def sqr(n) {
squared =1
for(inti =2; i <= n; i +=2) {
squared<
}
squared
}
代碼調用:
println "the sum of even number from 1 to 10 is${sum(10)}"
println "the production of even number from 1 to 10 is${product(10)}"
println "the squares of even number from 1 to 10 is${sqr(10)}"
結果:
the sum of even number from 1 to 10 is 30
the production of even number from 1 to 10 is 3840
the squares of even number from 1 to 10 is 1
可以發現:使用傳統代碼時,盡管方法及其相似,卻不能以一種簡單的方式將這些方法進行統一管理,代碼冗余很大。在引進閉包后,這種問題迎刃而解。例子:
def ?pickEvent(n,block) {
for(inti =2; i <= n; i +=2) {
block(i)//此處為閉包作為參數傳遞后的代碼
}
}
此時,一個帶閉包參數的方法就能夠解決很多問題:打印偶數、偶數求和、偶數乘積、偶數次冪 等等一類問題。例子:
打印偶數:
pickEvent(10,{number ->println number})//打印偶數,此處使用Lambda表達式
pickEvent(10,{println(it)})//打印偶數,當方法參數唯一時,可以在閉包調用時用it替換
pickEvent(10){println(it)}//打印偶數,當閉包是最后一個參數時,可以放于方法參數列表之后
結果都是:
2
4
6
8
10
偶數相加:
total =0
pickEvent(10) { total += it }
println total
偶數相乘:
pro =1
pickEvent(10){pro *= it }
println pro
偶數次冪:
sqrt =1
pickEvent(10){sqrt <
println sqrt
結果:
30
3840
1
這樣的代碼簡潔大方,而且復用性強。但值得注意的是:Groovy的閉包不能單獨存在,只能附到一個方法上,或者賦值給一個變量。
二、閉包的應用
普通方法在實現某個特定的目標明確的任務時要優于閉包,重構的過程是引入閉包的好時機。
閉包在使用時應該保持短小、有內聚性。閉包應該設計為附在方法上的小段代碼,只有幾行。
三、閉包的使用方式
由于Groovy的閉包不能單獨存在,只能附到一個方法上,或者賦值給一個變量。所以其使用方式有兩種:方法上作為參數,或者賦值給變量。例子:
引用閉包:
print 'total of event value from 1 to 10 is : '
println totalSelectValue(10,{it%2==0})
變量賦值:
print 'total of event value from 1 to 10 is : '
def isEven= {it%2==0}
println totalSelectValue(10,isEven)
結果:
total of event value from 1 to 10 is : 55
total of event value from 1 to 10 is : 55
四、向閉包傳遞參數
對于單個參數的閉包,it是該參數的默認名稱,只要知道只傳一個參數,就可以使用it,如果使用多個參數,就需要將參數一一列舉出來。例子:
方法定義:
def tellFortune(closure){
closure new Date("05/12/2017"),"Your day is fulled with ceremony"
}
代碼調用:
tellFortune(){date,fortune->
println "Fortune for${date} is ${fortune}"
}
結果:
Fortune for Fri May 12 00:00:00 CST 2017 is your day is fulled with ceremony
因為Groovy是可選類型,所以上面調用方法的代碼中添加參數類型,因此也可以這樣寫:
tellFortune(){Date date,fortune->
println "Fortune for${date} is ${fortune}"
}
一般地,盡量為參數取一個貼切的名字,通常是可以避免定義類型的
五、使用閉包進行資源清理
Java采用自動垃圾回收機制,開發者不需要處理內存分配和釋放。但是,不是所有資源都可以及時回收的,比如Java中寫/讀文件時需要關流;Android中,數據庫相關操作時需要手動關閉cursor,bitmap用完時需要recycle:這些操作會不經意被開發者忘記。因此我們可以通過閉包,以及引進Execute Around Method模式,進行合理、簡單的垃圾回收。Execute Around Method(查看)(To represent pairs of actions that have to be taken together, code a method that takes a Block as an argument. Name the method by appending "During: aBlock" to the name of the first method to be invoked. In the body of the Execute Around Method, invoke the first method, evaluate the block, then invoke the second method.)。例子:
類:
class Resource{
def open(){
println 'opening'
}
def read(){
println 'reading'
}
def write(){
println 'writing'
}
def close(){
println 'closing'
}
}
代碼調用:
def resource=new Resource()
resource.open()
resource.read()
resource.write()
結果:
opening
reading
writing
我們發現,不調用close方法,是不能正常結束流程的。這時候需要借助閉包:將需要調用的代碼以閉包作為參數放入一個靜態方法,在這個靜態方法中可以執行必須操作,閉包執行需求。例子:
在以上類中添加靜態方法:
def static use(closure){
def r=newResource()
try{
r.open()
closure
}finally{
r.close()
}
}
代碼調用:
Resource.use{re ->
re.read()
re.write()
}
結果:
opening
reading
writing
closing
六、閉包與協程
方法在執行過程中只有一個入口,方法完成后回到調用者的作用域,而協程支持多個入口,每個入口都是上次掛起調用的位置,我們可以進入一個函數,執行代碼,掛起,再回到調用者的上下文或者作用域內執行一些代碼。例子:
方法:
def iterate(n,closure){
1.upto(n){
println "In iterate with value${it}"
closure(it)
}
}
調用:
println 'Start..'
def total=1
iterate(4){
total+=it
println "In Closure with value${total}"
}
println 'Done..'
結果:
Start..
In iterate with value 1
In Closure with value 2
In iterate with value 2
In Closure with value 4
In iterate with value 3
In Closure with value 7
In iterate with value 4
In Closure with value 11
Done..
七、科里化閉包
帶有預綁定參數的閉包叫做科里化閉包,當對一個閉包調用curry()方法時,就要求綁定某些形參。在預先綁定了一個形參之后,調用閉包就不必再為這個參數傳遞實參()。例子:
def tellFortue(closure){
Date date=new Date("05/12/2017")
post Fortune=closure.curry(date)
postFortune "Your day is filled with ceremony"
postFortune "they are features ,not bug"
}
代碼調用:
tellFortue(){date,fortune ->
println "Fortune for${date}is${fortune}"
}
結果:
Fortune for Fri May 12 00:00:00 CST 2017 is Your day is filled with ceremony
Fortune for Fri May 12 00:00:00 CST 2017 is they are features ,not bugs
八、動態閉包
可以確定一個閉包是否已經提供(布爾值判斷),如果尚未提供,比如說一個算法,我們可以決定使用該方法的默認實現來代替調用者未能提供的特殊實現。例子:
方法:
def doSomthing(closure) {
if(closure) {
closure()
}else{
println 'No Closure provided for this method'
}
}
調用:
doSomthing(){
println 'A Closure was provided for this method'
}
doSomthing()
結果:
A Closure was provided for this method
No Closure provided for this method
在傳遞參數時也有很大靈活性,可以動態地確定一個閉包期望的參數數目和類型。在此基礎上,我們可以使用閉包的maximumNumberOfParameters屬性來判斷并對不同值做出不同的實現。例子:
def computeOrders(int amount,Closure closure) {
def interst=0
if(closure.maximumNumberOfParameters==2) {
interst=closure(amount,0.2)
}else{
interst=closure(amount)
}
println interst
}
調用:
computeOrders(100) {it*0.1}
computeOrders(100) {amount, interestRate -> amount*interestRate}
結果:
10.0
20.0
除了maximumNumberOfParameters屬性外,閉包還有parameterTypes供開發者確定不同的實現。例子:
類:
def examine(Closureclosure){
println "$closure.maximumNumberOfParametersis Provided"
for(paraminclosure.parameterTypes) {
println param.name
}
println "--"
}
方法調用:
examine{}
examine{Dateval ->}
examine{val ->}
examine{inta,intb,doublec ->}
結果:
1 param(s) is Provided
java.lang.Object
1 param(s) is Provided
java.util.Date
1 param(s) is Provided
java.lang.Object
3 param(s) is Provided
int
int
double
可以發現:調用一個空的閉包{}時,即沒有參數時,默認返回一個Object類的參數。
九、閉包委托
this、owner、delegate是閉包的三個屬性,用于確定哪個對象處理該閉包的方法調用,一般而言,delegate會設置為owner。閉包內this指向該閉包綁定的對象(正在執行的上下文),在閉包內引用的變量和方法都會綁定到this,如果this無法處理,轉向owner,最后再轉向delegate。
十、使用尾遞歸編寫程序
(略)
十一、使用記憶化改善性能
(略)
《完》