Groovy語法(二):閉包

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
發佈了40 篇原創文章 · 獲贊 6 · 訪問量 4785
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章