Groovy閉包理解

Allen716發表於2020-10-06

 

閉包(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 一樣。。。


原文地址 :https://www.jianshu.com/p/6dc2074480b8

相關文章