二、Groovy语法(二):闭包

Groovy闭包

1、Groovy中闭包基础

  • 1.1 闭包的概念
    闭包是被包装成对象的代码块,可以通过一个变量引用到它,也可以传递给别人进行处理(像处理一个对象一样处理闭包,比如作为参数传递、作为一个方法的返回值等)

  • 1.2 闭包的定义和调用

//定义一个闭包(闭包是一些代码组成的代码块对象,用{}括起来的一段代码)
def closure = { println 'Hello groovy!'}
//调用(类似定义了一个方法,然后可以去调用这个方法,可与方法对比着来理解闭包)
closure.call() //Hello groovy!
closure() //Hello groovy!
  • 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]
  • 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

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
*/
  • 经过上面的推导之后,相信对于闭包的使用已经问题不大了,只要知道了与之结合使用的方法的运作方式,即可慢慢理解

  • 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]
  • 2.3 与数据结构的结合使用
这部分内容放到讲解Groovy中的数据结构中

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类(脚本文件)
//========对指向最近的内部类进一步说明============

//在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
  • 说了这么多,可以看到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

  • 可以看到,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是定义的时候就确定了的,无法再次修改

总结:

  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,从名字也可猜出各自的作用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容