1、Groovy中閉包基礎
•1.1 閉包的概念
閉包是被包裝成對象的代碼塊,可以通過一個變量引用到它,也可以傳遞給別人進行處理(像處理一個對象一樣處理閉包,比如作爲參數傳遞、作爲一個方法的返回值等)
•1.2 閉包的定義和調用
//定義一個閉包(閉包是一些代碼組成的代碼塊對象,用{}括起來的一段代碼)
def closure = { println 'Hello groovy!'}
//調用(類似定義了一個方法,然後可以去調用這個方法,可與方法對比着來理解閉包)
closure.call() //Hello groovy!
closure() //Hello groovy!
12345
•1.3閉包參數(普通參數和隱式參數)
//定義一個有參數的閉包 利用->區分參數和具體執行代碼
def closure = { String name -> println "Hello $name!"}
//調用
closure('groovy') //Hello groovy!
//多個參數由逗號隔開
def closure2 = { String name,int age -> println "Hello $name! My age is $age"}
//調用
closure2('groovy',6) //Hello groovy! My age is 6
//當沒有聲明參數時,每個閉包都會有一個默認的參數it指代傳入的參數
//只有一個參數時可考慮使用該方式聲明閉包
//注意若聲明瞭有一個參數,該閉包就沒有這個it參數了!!
def closure3 = {println it}
closure3('hello groovy!') //hello groovy!
closure3([1,2,3]) //[1, 2, 3]
12345678910111213141516
•1.4 閉包的返回值
//閉包的返回值
def closure = { String name -> return "Hello $name!"}
def result = closure('groovy')
println result //Hello groovy! 返回值就是return 的內容
def closure1 = { println it}
def result1 = closure1("groovy")
println result1 //null ,所有的閉包都有返回值,若沒有寫返回,則返回null
12345678910111213
2、Groovy中閉包使用(常用的四種)
•2.1 與基本類型的結合使用
//只列出幾項,更多的方法到DefaultGroovyMethods類中查看
def x = fab(5)
def x2 = fab2(5)
println x //120
println x2 //120
/**
* 求指定num的階乘
*/
int fab(int number) {
def result = 1
//從1開始,依次遞增到number,每次遞增的結果傳入閉包進行處理
1.upto(number, { num -> result *= num })
return result
}
/*
上面的是什麼意思呢? num就是遞增變化的那個參數 它從1~number
第一步,result = 1; num=1; 傳入執行 result = result*num ==> result = 1*1 = 1
第二步,result = 1; num=2; 傳入執行 result = result*num ==> result = 1*2 = 2
第三步,result = 2; num=3; 傳入執行 result = result*num ==> result = 2*3 = 6
第四步,result = 6; num=4; 傳入執行 result = result*num ==> result = 6*4 = 24
第五步,result = 24; num=5; 傳入執行 result = result*num ==> result = 24*5 = 120
*/
/**
*求指定num的階乘
*/
int fab2(int number) {
def result = 1
//從number開始,依次遞減到1,每次遞減的結果傳入閉包進行處理
number.downto(1){ num -> result *= num }
return result
}
/*
同樣的 num就是遞減變化的那個參數 它從number~1
第一步,result = 1; num=5; 傳入執行 result = result*num ==> result = 1*5 = 5
第二步,result = 5; num=4; 傳入執行 result = result*num ==> result = 5*4 = 20
第三步,result = 20; num=3; 傳入執行 result = result*num ==> result = 20*3 = 60
第四步,result = 60; num=2; 傳入執行 result = result*num ==> result = 60*2 = 120
第五步,result = 120; num=1; 傳入執行 result = result*num ==> result = 120*1 = 120
*/
//注意看fab2()方法中downto()方法的寫法,當方法中的最後一個參數是閉包時,可以將閉包寫在括號的外面,若該方法僅有一個閉包參數,除了可以將閉包寫在外面,還可以將括號省略,如下所示
/**
* 累加 從0累加到(number-1)
* times方法始終從0開始到number-1循環
*/
int accumulate(int number) {
def result = 0
number.times { num -> result += num }
return result
}
def sum = accumulate(5)
println sum //0+1+2+3+4 = 10
//相信大家都可以理解上面的運行原理,可以自己按照上面的方式先推一遍,以便更好的理解閉包的使用
/*
同樣的 num就是遞增變化的那個參數 它從number~1
第一步,result = 0; num=0; 傳入執行 result = result+num ==> result = 0+0 = 0
第二步,result = 0; num=1; 傳入執行 result = result+num ==> result = 0+1 = 1
第三步,result = 1; num=2; 傳入執行 result = result+num ==> result = 1+2 = 3
第四步,result = 3; num=3; 傳入執行 result = result+num ==> result = 3+3 = 6
第五步,result = 6; num=4; 傳入執行 result = result+num ==> result = 6+4= 10
*/
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
• 經過上面的推導之後,相信對於閉包的使用已經問題不大了,只要知道了與之結合使用的方法的運作方式,即可慢慢理解
•2.2 與String的結合使用
def str = "the 2 add 3 is 5"
//each方法遍歷str的每一個字符,都執行閉包中的操作,並返回它自己。每個字符都輸出兩遍
str.each { print it.multiply(2)} //tthhee 22 aadddd 33 iiss 55
//find方法查找符合條件的第一個,找到即停止並返回,沒找到返回null
println str.find {String s -> return s.isNumber() } //2
//findAll方法查找所有符合條件的內容,返回所有符合條件的內容的集合
def listResult = str.findAll { s -> s.isNumber() }
println listResult.toListString() //[2, 3, 5] ,集合都可以轉爲listString的形式
//注意find和findAll方法閉包的返回值都需要是Boolean的,注意看findAll方法,閉包中的最後表達式有值時,即作爲閉包的返回值,可省略return,參數類型可推導,省略
//any判斷是否有符合條件的內容,有一個,即返回true,否則返回false
println str.any { s -> s.isNumber() } //true
//every方法判斷是否所有的內容都符合條件,是返回true,否則返回false
println str.every { s -> s.isNumber() } //false
//collect方法對每一個元素都執行閉包中的操作,並將每一個操作過後的結果添加到一個新的ArrayList中
def list2 = str.collect { it.toUpperCase() }
println list2 //[T, H, E, , 2, , A, D, D, , 3, , I, S, , 5]
//題外話,對list的collect操作
//對list的collect操作,去除所有的空格並且轉換爲大寫
println str.findAll {
String s -> !s.isBlank()}.collect { it.toString().toUpperCase() } // [T, H, E, 2, A, D, D, 3, I, S, 5]
12345678910111213141516171819202122232425262728
3、Groovy中閉包進階
•3.1、閉包的關鍵變量(this,owner,delegeta)
this: 代表定義閉包處的類
owner: 代表閉包定義處的類或者對象
delegeta: 可代表做任意的對象,默認與owner一致,可手動指定
/**
* 閉包中三個重要的變量:this,owner,delegate
*/
def scriptClosure = {
println "scriptClosure this:" + this
println "scriptClosure owner:" + owner
println "scriptClosure delegate:" + delegate
}
scriptClosure.call()
/*
* 輸出結果
*/
//scriptClosure this:variable.ClosureStudy@3232a28a
//scriptClosure owner:variable.ClosureStudy@3232a28a
//scriptClosure delegate:variable.ClosureStudy@3232a28a
//可以看到它們都指向了ClosureStudy類對象(即定義它們的類或者說距離最近的那個封閉類)
//ps:ClosureStudy.groovy在編譯後會在out目錄下生成一個繼承Script的java類(腳本文件)
1234567891011121314151617181920
//========對指向最近的內部類進一步說明============
//在ClosureStudy類中再定義了一個內部類
class InnerClass {
//定義了一個靜態閉包
def static classClosure = {
println "classClosure this:" + this
println "classClosure owner:" + owner
println "classClosure delegate:" + delegate
}
//定義了一個靜態方法
def static mTestMethod() {
//在方法中定義一個閉包
def methodClosure = {
println "methodClosure this:" + this
println "methodClosure owner:" + owner
println "methodClosure delegate:" + delegate
}
//調用
methodClosure.call()
}
}
//調用閉包和方法
def innerClass = new InnerClass()
innerClass.classClosure.call()
innerClass.mTestMethod()
/*
*輸出結果
*/
//classClosure this:class variable.InnerClass
//classClosure owner:class variable.InnerClass
//classClosure delegate:class variable.InnerClass
//methodClosure this:class variable.InnerClass
//methodClosure owner:class variable.InnerClass
//methodClosure delegate:class variable.InnerClass
//可以看到它們都指向了定義它們的那個類的字節碼,注意因爲是靜態的,所以指向的都是字節碼(結果後面沒有@xxx)。
/*
*去掉static關鍵字後運行結果
*/
//classClosure this:class variable.InnerClass@6cd28fa7
//classClosure owner:class variable.InnerClass@6cd28fa7
//classClosure delegate:class variable.InnerClass@6cd28fa7
//methodClosure this:class variable.InnerClass@6cd28fa7
//methodClosure owner:class variable.InnerClass@6cd28fa7
//methodClosure delegate:class variable.InnerClass@6cd28fa7
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
• 說了這麼多,可以看到this,owner,delegete都是一樣的值,那麼對於owner的說明中,“指代定義閉包處的類或者對象”,指代“對象”又是怎麼一回事呢?
//ClosureStudy中定義一個閉包
def nestClosure = {
//在閉包中再定義一個閉包
def innerClosure = {
println "innerClosure this:" + this
println "innerClosure owner:" + owner
println "innerClosure delegate:" + delegate
}
innerClosure.call()
}
nestClosure.call()
/*
*輸出結果
*/
//innerClosure this:variable.ClosureStudy@6cd28fa7
//innerClosure owner:variable.ClosureStudy$_run_closure3@4fb3ee4e
//innerClosure delegate:variable.ClosureStudy$_run_closure3@4fb3ee4e
12345678910111213141516171819
• 可以看到,this依然指向定義它的ClosureStudy類,但是owner和delegate卻指向了ClosureStudy中的對象_run_closure3@4fb3ee4e。還記得閉包的概念嗎?閉包是被包裝成對象的代碼塊,閉包就是一個對象(Closure類型的對象),所以在閉包中定義的閉包的owner實際上指向了定義它的那個閉包對象,而delegate的指向默認與owner一致,所以他也指向了定義閉包的那個閉包對象。下面再看一下delegate的指定。
//==========delegate的指定===========
class TestChangeDelegateClass{
}
def testChangeDelegateClass = new TestChangeDelegateClass()
//定義一個閉包
def nestClosure = {
//在閉包中再定義一個閉包
def innerClosure = {
println "innerClosure this:" + this
println "innerClosure owner:" + owner
println "innerClosure delegate:" + delegate
}
innerClosure.delegate = testChangeDelegateClass //修改默認的delegate
innerClosure.call()
}
nestClosure.call()
/*
*輸出結果
*/
//innerClosure this:variable.ClosureStudy@57cf54e1
//innerClosure owner:variable.ClosureStudy$_run_closure3@17497425
//innerClosure delegate:variable.TestChangeDelegateClass@f0da945
//可以看到delegate變爲我們指定的TestChangeDelegateClass對象。this和owner是定義的時候就確定了的,無法再次修改
12345678910111213141516171819202122232425262728
總結:
1.this指向定義閉包處的類,在定義的時候就確定,無法再次修改
2.owner在閉包定義在類與方法裏的時候指向定義它的類(最近的一個),而閉包被定義在閉包中時,該閉包的owner指向定義該閉包的那個閉包對象,同樣的在定義的時候就確定,無法再次修改
3.delegate默認指向和woner一致,當人爲的修改後,delegate指向修改後的那個對象
•3.2、閉包的委託策略(this,owner,delegate的作用)
class Student {
def name
def self_introduction = { "My name is $name" }
@Override
String toString() {
return self_introduction.call()
}
}
class Teacher {
def name
}
//在初始化的時候傳入值(先知道可以這樣傳,在Groovy面向對象中會講解)
def stu = new Student(name: 'groovy')
def tea = new Teacher(name: 'java')
println stu.toString() //輸出My name is groovy
//上面的輸出結果毫無疑問,那麼有沒有什麼辦法不修改學生的name的情況下讓輸出的name不是學生的,而是老師的name呢?
//首先閉包self_introduction中的name肯定是指向定義它的類student的name,前面說過閉包的delegate是可以修改的,我們修改閉包self_introduction的delegate指向Teacher,會怎麼樣呢?
//修改之後再打印
stu.self_introduction.delegate = tea
println stu.toString() //My name is groovy
//發現並沒有起作用,還是輸出了學生的名字。這個時候就需要閉包的委託策略了
//指定閉包的委託策略爲Closure.DELEGATE_FIRST
stu.self_introduction.resolveStrategy = Closure.DELEGATE_FIRST
println stu.toString() //My name is java
//每個閉包都有自己的委託策略,默認是Closure.OWNER_FIRST,表明閉包中的變量或是方法都是先從閉包指向的owner中尋找
//定義一個teacher2,屬性爲name1
class Teacher2 {
def name1
}
def tea2 = new Teacher2(name1: 'java')
//修改委託策略
stu.self_introduction.resolveStrategy = Closure.DELEGATE_FIRST
//指定delegate爲tea2
stu.self_introduction.delegate = tea2
println stu.toString() //My name is groovy
//此時由於在tea2中沒有找到name屬性,所以又會從owner中尋找,因此輸出的是groovy
//四種委託策略:Closure.DELEGATE_FIRST,Closure.OWNER_FIRST,另外還有Closure.DELEGATE_ONLY,Closure.OWNER_ONLY,從名字也可猜出各自的作用。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849