1. 程式人生 > >Groovy中的閉包

Groovy中的閉包

Closures(閉包)

本節主要講groovy中的一個核心語法:closurs,也叫閉包。閉包在groovy中是一個處於程式碼上下文中的開放的,匿名程式碼塊。它可以訪問到其外部的變數或方法。

1. 句法

1.1 定義一個閉包

{ [closureParameters -> ] statements }

其中[]內是可選的閉包引數,可省略。當閉包帶有引數,就需要->來將引數和閉包體相分離。

下面看一些閉包的具體例子:

{ item++ }                                          

{ -> item++ }                                       

{ println it }                                      

{ it -> println it }                                

{ name -> println name }                            

{ String x, int y ->                                
    println "hey ${x} the value is ${y}"
}

{ reader ->                                         
    def line = reader.readLine()
    line.trim()
}

1.2 閉包也是物件

閉包在groovy中是groovy.lang.Closure類的例項,這使得閉包可以賦值給變數或欄位。

def listener = { e -> println "Clicked on $e.source" }      
assert listener instanceof Closure
Closure callback = { println 'Done!' }                      
Closure<Boolean> isTextFile = {
    File it -> it.name.endsWith('.txt')                     
}

1.3 閉包的呼叫

閉包有兩種呼叫方式:

def code = { 123 }

assert code() == 123

assert code.call() == 123

閉包名+()或者閉包名.call()來呼叫閉包。

2. 引數

2.1 正常引數

閉包的引數型別和前面講的方法的引數型別一樣,這裡不多說。

2.2 含蓄的引數

當閉包沒有顯式宣告引數時,其預設包含一個隱式的引數it

def greeting = { "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'

2.3 引數列表

引數列表的用法與普通方法一樣,這裡不多贅述。

3. 委託策略

委託策略是groovy中閉包獨有的語法,這也使得閉包較java的lambda更為高階。下面簡單介紹一下groovy中的委託策略。

3.1 Owner,delegate和this

在理解delegate之前,首先先要了解一下閉包中this和owner的含義,閉包中三者是這麼定義的:

  • this 表示定義閉包的外圍類。
  • owner 表示定義閉包的直接外圍物件,可以是類或者閉包。
  • delegate 表示一個用於處理方法呼叫和屬性處理的第三方類。

3.1.1 This

閉包中,使用this關鍵字或者呼叫方法getThisObject()來獲得其外圍類:

class Enclosing {
    void run() {
        def whatIsThisObject = { getThisObject() }          
        assert whatIsThisObject() == this                   
        def whatIsThis = { this }                           
        assert whatIsThis() == this                         
    }
}
class EnclosedInInnerClass {
    class Inner {
        Closure cl = { this }                               
    }
    void run() {
        def inner = new Inner()
        assert inner.cl() == inner                          
    }
}
class NestedClosures {
    void run() {
        def nestedClosures = {
            def cl = { this }                               
            cl()
        }
        assert nestedClosures() == this                     
    }
}

判斷this表示的具體是哪個物件可以從this往外找,遇到的第一類就是this代表的類。

3.1.2 Owner

owner與this類似,只不過owner表示的是直接外圍物件,可以是類也可以是閉包:

class Enclosing {
    void run() {
        def whatIsOwnerMethod = { getOwner() }               
        assert whatIsOwnerMethod() == this                   
        def whatIsOwner = { owner }                          
        assert whatIsOwner() == this                         
    }
}
class EnclosedInInnerClass {
    class Inner {
        Closure cl = { owner }                               
    }
    void run() {
        def inner = new Inner()
        assert inner.cl() == inner                           
    }
}
class NestedClosures {
    void run() {
        def nestedClosures = {
            def cl = { owner }                               
            cl()
        }
        assert nestedClosures() == nestedClosures            
    }
}

上述例子與this中的例子不同的就是NestedClosures,其中owner表示的是nestedClosures而不是NestedClosures。

3.1.3 Delegate

閉包中可以使用delegate關鍵字或者getDelegate()方法來得到delegate變數,它預設與owner一致,但可以由使用者自定義其代表的物件。

class Enclosing {
    void run() {
        def cl = { getDelegate() }                          
        def cl2 = { delegate }                              
        assert cl() == cl2()                                
        assert cl() == this                                 
        def enclosed = {
            { -> delegate }.call()                          
        }
        assert enclosed() == enclosed                       
    }
}

閉包中的delegate可被指向任意物件,我們看下面這個例子:

class Person {
    String name
}
class Thing {
    String name
}

def p = new Person(name: 'Norman')
def t = new Thing(name: 'Teapot')

定義了兩個擁有相同屬性name的類Person和Thing。接著定義一個閉包,其作用是通過delegate來獲得name屬性。

def upperCasedName = { delegate.name.toUpperCase() }

接著改變閉包的delegate的指向,我們可以看到閉包呼叫結果也不同:

upperCasedName.delegate = p
assert upperCasedName() == 'NORMAN'
upperCasedName.delegate = t
assert upperCasedName() == 'TEAPOT'

3.1.4 Delegate策略

在閉包中,當一個屬性沒有指明其所有者的時候,delegate策略就會發揮作用了。

class Person {
    String name
}
def p = new Person(name:'Igor')
def cl = { name.toUpperCase() }   //❶              
cl.delegate = p                   //❷              
assert cl() == 'IGOR'             //❸

可以看到❶處的name沒有指明其所有者。即這個name屬性壓根不知道是誰的。在❷處指明cl的delegate為p,這時候在❸處呼叫成功。

以上程式碼之所以可以正常執行是因為name屬性會被delegate處理。這是一個十分強大的方式用於解決閉包內的屬性的訪問或方法的呼叫。在❶處沒有顯示的使用delegate.name是因為delegate策略已經在程式執行的時候幫助我們這樣做了。下面我們看看閉包擁有的不同的delegate策略:

  • Closure.OWNER_FIRST 這是預設的策略,優先從owner中尋找屬性或方法,找不到再從delegete中尋找。上面的例子就是因為在owner中沒有找到name,接著在delegate中找到了name屬性。
  • Closure.DELEGATE_FIRST 與OWNER_FIRST相反。
  • Closure.OWNER_ONLY 只在owner中尋找。
  • Closure.DELEGATE_ONLY 只在delegate中尋找。
  • Closure.TO_SELF 在閉包自身中尋找。

下面我們看一下預設的Closure.OWNER_FIRST的用法:

class Person {
    String name
    def pretty = { "My name is $name" }             
    String toString() {
        pretty()
    }
}
class Thing {
    String name                                     
}

def p = new Person(name: 'Sarah')
def t = new Thing(name: 'Teapot')

assert p.toString() == 'My name is Sarah'           
p.pretty.delegate = t                       //❶                          
assert p.toString() == 'My name is Sarah'   //❷

儘管在❶處將delegate指向了t,但因為是owner first的緣故,還是會優先使用Person的name屬性。

略做修改:

p.pretty.resolveStrategy = Closure.DELEGATE_FIRST
assert p.toString() == 'My name is Teapot'

這時候就會訪問t的name屬性了。

下面再來看一個例子:

class Person {
    String name
    int age
    def fetchAge = { age }
}
class Thing {
    String name
}

def p = new Person(name:'Jessica', age:42)
def t = new Thing(name:'Printer')
def cl = p.fetchAge
cl.delegate = p
assert cl() == 42
cl.delegate = t
assert cl() == 42
cl.resolveStrategy = Closure.DELEGATE_ONLY
cl.delegate = p
assert cl() == 42
cl.delegate = t
try {
    cl()
    assert false
} catch (MissingPropertyException ex) {
    // "age" is not defined on the delegate
}

當使用了Closure.DELEGATE_ONLY後,若delegate中找不到age屬性,則會直接報錯。

4. GStrings中的閉包

先來看一下下面這段程式碼:

def x = 1
def gs = "x = ${x}"
assert gs == 'x = 1'

OK,執行沒有問題,那如果加兩行程式碼呢?

x = 2
assert gs == 'x = 2'

這裡就會報錯了,錯誤原因有兩:

  • GString只是呼叫了字串的toString方法來獲得值。
  • ${x}這種寫法並不是一個閉包,而是一個表示式等價於$x,當GString被建立的時候該表示式會被計算。

所以當給x賦值2的時候,gs已經被建立,表示式也已經被計算,結果是x = 1,所以gs得值就是固定的x = 1。

如果要在GString使用閉包也是可以的,如下:

def x = 1
def gs = "x = ${-> x}"
assert gs == 'x = 1'

x = 2
assert gs == 'x = 2'

總結

到這裡groovy中閉包的基本用法結束了,更多閉包的用請參考

還記得我們學習groovy的目的是什麼嗎?對了,就是gradle。而且在gradle中使用了大量閉包的概念,所以在學習gradle之前還請好好掌握閉包這一節內容。