Groovy閉包理解
閉包(Closure)是很多程式語言中很重要的概念,那麼Groovy中閉包是什麼,官方定義是“Groovy中的閉包是一個開放,匿名的程式碼塊,可以接受引數,返回值並分配給變數”,簡而言之,他說一個匿名的程式碼塊,可以接受引數,有返回值,那麼到底是怎麼樣的,我們來探究一下:
- 如何定義
定義閉的語意 :{ [closureParameters -> ] statements }
其中[closureParameters->]代表引數們,多引數用逗號分割,用->隔開引數與內容,沒有引數可以不寫->
- 閉包的寫法
//執行一句話
{ printf 'Hello World' }
//閉包有預設引數it,且不用申明
{ println it }
//閉包有預設引數it,申明瞭也無所謂
{ it -> println it }
// name是自定義的引數名
{ name -> println name }
//多個引數的閉包
{ String x, int y ->
println "hey ${x} the value is ${y}"
}
- groovy.lang.Closure物件
其實,每定義的閉包是一個Closure物件,我們可以把一個閉包賦值給一個變數
def innerClosure = {
printf("hello")
}
def hello = { String x ->
printf("hello ${x}")
}
我們把閉包作為方法的引數型別
void setOnClickListener(Closure closure) {
this.onClickListener = closure
}
如何執行閉包物件呢,執行閉包物件有兩種,一是直接用括號+引數,二是呼叫call方法
//執行innerClosure 閉包
innerClosure ()
//or
innerClosure.call()
//帶引數的閉包
hello("world")
//or
hello.call("world")
- 理解閉包內this,owner,delegate物件
在閉包內部,有三個內建物件this,owner,delegate,我們可以直接this,owner,delegate呼叫,或者用get方法:
- getThisObject() 等於 this
- getOwner() 等於 owner
- getDelegate() 等於delegate
那麼這三個物件,分別指代的是哪個物件,是否和java的this關鍵字一樣,我們先做文字解釋:
- this 對應於定義閉包的那個類,如果在內部類中定義,指向的是內部類
- owenr 對應於定義閉包的那個類或者閉包,如果在閉包中定義,對應閉包,否則同this一致
- delegate 預設是和owner一致,或者自定義delegate指向
我們來用程式碼驗證一下
class OuterClass {
class InnerClass {
def outerClosure = {
def innerClosure = {
}
printfMsg("innerClosure", innerClosure)
println("------")
printfMsg("outerClosure", outerClosure)
}
void printfMsg(String flag, Closure closure) {
def thisObject = closure.getThisObject()
def ownerObject = closure.getOwner()
def delegate = closure.getDelegate()
println("${flag} this: ${thisObject.toString()}")
println("${flag} owner: ${ownerObject.toString()}")
println("${flag} delegate: ${delegate.toString()}")
}
}
def callInnerMethod() {
def innerClass = new InnerClass()
innerClass.outerClosure.call()
println("------")
println("outerClosure toString ${innerClass.outerClosure.toString()}")
}
static void main(String[] args) {
new OuterClass().callInnerMethod()
}
}
我們在OuterClass定義一個內部類InnerClass,在InnerClass中定義了一個outerClosure閉包,在outerClosure中定義了一個innerClosure閉包,現在我們分別列印innerClosure和outerClosure閉包對應的this,owner,delegate物件和outerClosure物件的toString方法
innerClosure this: com.example.groovy.bean.OuterClass$InnerClass@e874448
innerClosure owner: com.example.groovy.bean.OuterClass$InnerClass$_closure1@5bfbf16f
innerClosure delegate: com.example.groovy.bean.OuterClass$InnerClass$_closure1@5bfbf16f
------
outerClosure this: com.example.groovy.bean.OuterClass$InnerClass@e874448
outerClosure owner: com.example.groovy.bean.OuterClass$InnerClass@e874448
outerClosure delegate: com.example.groovy.bean.OuterClass$InnerClass@e874448
------
outerClosure toString com.example.groovy.bean.OuterClass$InnerClass$_closure1@5bfbf16f
分析結果:
- innerClosure
- this:結果是OuterClass$InnerClass物件
- owner:結果是OuterClass$InnerClass$_closure1物件 ,即outerClosure
- delegate:同owenr
- **outerClosure **
- this:結果是OuterClass$InnerClass物件
- owner:結果是OuterClass$InnerClass物件
- delegate:同owenr
this,owner ,delegate指向總結:
this 永遠是指定義該閉包類,如果存在內部類,則是最內層的類,但this不是指當前閉包物件
owenr 永遠是指定義該閉包的類或者閉包,顧名思義,閉包只能定義在類中或者閉包中
** delegate** 預設是指owner,可以自己設定,自己設定的話又是什麼情況
- delegate才是重頭戲
前面已經說了,閉包可以設定delegate物件,設定delegate的意義就是講閉包和一個具體的物件關聯起來,這個如何理解,看程式碼:
# Person.groovy
class Person {
String name
int age
void eat(String food) {
println("你喂的${food}真難吃")
}
@Override
String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}'
}
}
# Main.groovy
def cc = {
name = "hanmeimei"
age = 26
}
我們定義Person實體類,再定義一個名字叫cc的閉包,我們想在閉包裡修改Person的name和age,還想呼叫eat方法,這個怎麼關聯起來?
cc.delegate = person
cc.call()
怎麼,這樣就ok了嗎,是的,就是這麼簡單,完整程式碼
class Main {
def cc = {
name = "hanmeimei"
age = 26
eat("油條")
eat "油條"
}
static void main(String... args) {
Main main = new Main()
Person person = new Person(name: "lilei", age: 14)
println person.toString()
main.cc.delegate = person
main.cc.call()
println person.toString()
}
}
#列印結果
Person{name='lilei', age=14}
你喂的油條真難吃
Person{name='hanmeimei', age=26}
上面我們知道了,在閉包中可以訪問被代理物件的屬性和方法,哦,那麼我還有一個疑問,如果閉包所在的類或閉包中和被代理的類中有相同名稱的方法,到底要呼叫哪個方法,其實這個問題groovy肯定考慮到了,為我們設定了幾個代理的策略:
-
Closure.OWNER_FIRST是預設策略。優先在owner尋找,owner沒有再delegate
-
Closure.DELEGATE_FIRST:優先在delegate尋找,delegate沒有再owner
-
Closure.OWNER_ONLY:只在owner中尋找
-
Closure.DELEGATE_ONLY:只在delegate中尋找
-
Closure.TO_SELF:暫時沒有用到,哎不知道啥意思
為了驗證,我們現在修改一下Main.groovy程式碼
class Main {
void eat(String food){
println "我根本不會吃,不要餵我${food}"
}
def cc = {
name = "hanmeimei"
age = 26
eat("油條")
}
static void main(String... args) {
Main main = new Main()
Person person = new Person(name: "lilei", age: 14)
println person.toString()
main.cc.delegate = person
// main.cc.setResolveStrategy(Closure.DELEGATE_FIRST)
main.cc.setResolveStrategy(Closure.OWNER_FIRST)
main.cc.call()
println person.toString()
}
}
我們在Main中也定義了同名的方法eat(food),因為當前cc閉包的owner正是Main物件,我們通過呼叫setResolveStrategy方法,修改策略,發現結果和預期的一致
閉包delegate的基本概念已經講完,看完這些,相信你能進一步理解android開發中build.gradle中的寫法,已經到達本次學習的目的,下面我們練習一下:
閉包練習
實現一個回撥介面
做Android開發的同學對回撥介面肯定不陌生,特別是事件的監聽,現在,我們仿View.setOnClickListener用來閉包來實現一個回撥介面
class View {
private Closure onClickListener
Timer timer
View() {
timer = new Timer()
timer.schedule(new TimerTask() {
@Override
void run() {
perOnClick()
}
}, 1000, 3000)
}
void setOnClickListener(Closure closure) {
this.onClickListener = closure
}
private void perOnClick() {
if (onClickListener != null) {
onClickListener(this)
}
}
@Override
String toString() {
return "this is view"
}
}
定義一個View類,用Timer計時模擬事件的觸發,暴露setOnClickListener方法,用於介紹閉包物件,那麼呼叫者該如何寫呢?
View view = new View()
view.setOnClickListener { View v ->
println v.toString()
}
其實呼叫者也很簡單,只需定義一個閉包,v是傳遞過來的引數,列印出toString方法,結果如下
this is view
this is view
this is view
...
仿照Android DSL 定義閉包
在Android中我們熟悉的build.gradle配置,其實也是閉包,這下面肯定是你熟悉的程式碼
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
}
}
我們要實現自己的閉包,我們要定義兩個實體類Android.groovy和ProductFlavor.groovy,程式碼如下
# Android.groovy
class Android {
private int mCompileSdkVersion
private String mBuildToolsVersion
private ProductFlavor mProductFlavor
Android() {
this.mProductFlavor = new ProductFlavor()
}
void compileSdkVersion(int compileSdkVersion) {
this.mCompileSdkVersion = compileSdkVersion
}
void buildToolsVersion(String buildToolsVersion) {
this.mBuildToolsVersion = buildToolsVersion
}
void defaultConfig(Closure closure) {
closure.setDelegate(mProductFlavor)
closure.setResolveStrategy(Closure.DELEGATE_FIRST)
closure.call()
}
@Override
String toString() {
return "Android{" +
"mCompileSdkVersion=" + mCompileSdkVersion +
", mBuildToolsVersion='" + mBuildToolsVersion + '\'' +
", mProductFlavor=" + mProductFlavor +
'}'
}
}
# ProductFlavor.groovy
class ProductFlavor {
private int mVersionCode
private String mVersionName
private int mMinSdkVersion
private int mTargetSdkVersion
def versionCode(int versionCode) {
mVersionCode = versionCode
}
def versionName(String versionName) {
mVersionName = versionName
}
def minSdkVersion(int minSdkVersion) {
mMinSdkVersion = minSdkVersion
}
def targetSdkVersion(int targetSdkVersion) {
mTargetSdkVersion = targetSdkVersion
}
@Override
String toString() {
return "ProductFlavor{" +
"mVersionCode=" + mVersionCode +
", mVersionName='" + mVersionName + '\'' +
", mMinSdkVersion=" + mMinSdkVersion +
", mTargetSdkVersion=" + mTargetSdkVersion +
'}'
}
}
這兩個實體,相當於閉包的被代理物件,那麼我們閉包怎麼寫呢
//閉包定義
def android = {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
}
}
//呼叫
Android bean = new Android()
android.delegate = bean
android.call()
println bean.toString()
//列印結果
Android{mCompileSdkVersion=25, mBuildToolsVersion='25.0.2', mProductFlavor=ProductFlavor{mVersionCode=1, mVersionName='1.0', mMinSdkVersion=15, mTargetSdkVersion=25}}
結果很明顯,閉包申明的值,賦給了兩個實體物件Android和ProductFlavor,這種從閉包到具體類的代理過程,才是閉包最魅力的地方所在。
閉包語義解析
在閉包中,訪問代理物件的屬性,用"="符合,訪問代理物件的方法,用"()"或者空格,如果方法引數型別是Closure型別,可以直接用大括號申明閉包,就像android下的defaultConfig 一樣。。。
相關文章
- 理解“閉包”
- 理解閉包
- 理解JavaScript 閉包JavaScript
- 理解 JavaScript 閉包JavaScript
- PHP 閉包的理解PHP
- 理解Javascript的閉包JavaScript
- js閉包的理解JS
- 深入理解閉包
- 深入理解javascript原型和閉包(15)——閉包JavaScript原型
- 對JS閉包的理解JS
- 對javascript閉包的理解JavaScript
- javascript閉包的個人理解JavaScript
- Golang中閉包的理解Golang
- 理解 JavaScript 中的閉包JavaScript
- 深入理解JS閉包JS
- 面試題:如何理解閉包面試題
- 理解Python函式閉包Python函式
- 深入理解swift的閉包Swift
- [譯]理解JS中的閉包JS
- 理解C#中的閉包C#
- JS-閉包(closure)的理解JS
- 【譯】理解Rust中的閉包Rust
- Swift4.0 sorted(by:)函式理解(閉包$理解)Swift函式
- 【閉包概念】關於閉包概念不同解讀——你可以自己理解。
- 全面理解Javascript閉包和閉包的幾種寫法及用途JavaScript
- 用“揹包”去理解Go語言中的閉包Go
- 閉包的理解-from my own opinion
- 【譯】理解JavaScript閉包——新手指南JavaScript
- 徹底理解閉包實現原理
- 面試:對javascript的閉包的理解面試JavaScript
- 徹底理解js中的閉包JS
- javascript閉包的理解和例項JavaScript
- python閉包 - 理解與應用Python
- 簡單而清楚地理解閉包
- 閉包函式(匿名函式)的理解函式
- 深入理解javascript系列(七):閉包(1)JavaScript
- 深入理解javascript系列(八):閉包(2)JavaScript
- 深入理解javascript原型和閉包系列JavaScript原型