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之前還請好好掌握閉包這一節內容。😀