二、Groovy语法(二):闭包

久未见 提交于 2020-03-12 08:56:50

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,delegate)

/**
 * 闭包中三个重要的变量: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,delegate都是一样的值,那么对于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,从名字也可猜出各自的作用。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!