Groovy 闭包(官方文档)

本文深入探讨了Groovy语言中的闭包概念,包括闭包的定义、语法、参数、代理策略及其在GString上的应用。通过示例展示了闭包如何作为对象使用,以及在不同场景下的调用方式。此外,文章还详细解释了闭包的参数、隐藏参数、不定数量参数以及代理策略(如this、owner、delegate),并提供了具体的代码示例进行说明。
摘要由CSDN通过智能技术生成

 Groovy的闭包相当于一个匿名代码块,可以接受参数,返回值,也可以当做变量来使用。闭包可以引用它外围的变量,但是外围不能引用闭包内的变量,这好像是破坏代码结构的做法,但是也有其相对的好处。

1.语法

1.1 定义闭包

闭包的语法公式{[ 参数 ->] 语句/返回值}
参数:可以是0到多个有类型或不声明类型的参数,用“->”和语句隔开
下面是一些例子:

{ item++ }             这里只有一个返回值                             

{ -> item++ }          这里是表明必须无参                             

{ println it }         it在没有声明的情况下为默认参数                             

{ it -> println it }   作用等同上一句                             

{ name -> println name }        neme为无类型参数                    

{ String x, int y ->                                
    println "hey ${x} the value is ${y}"       两个有类型参数传入
}

{ reader ->                                         
    def line = reader.readLine()     虽然无类型声明,但可以直接调用其函数
    line.trim()
}
1.2 闭包做为一个对象

闭包其实是代表groovy.long.Closure类,但是闭包的形式可以方便的赋值给其他变量。

def listener = { e -> println "Clicked on $e.source" }      分配listener为一个Closure实例
assert listener instanceof Closure
Closure callback = { println 'Done!' }      显式赋值                
Closure<Boolean> isTextFile = {
    File it -> it.name.endsWith('.txt')     显式指定返回值         
}
1.3 调用闭包

闭包可以像方法一样调用,无参情况直接用: 闭包名()可调用,或用 闭包名.call()调用,传参则放入 () 内。下面是例子:

def code = { 123 }
assert code() == 123
assert code.call() == 123

def isOdd = { int i-> i%2 == 1 }                            
assert isOdd(3) == true                                     
assert isOdd.call(2) == false                               

def isEven = { it%2 == 0 }                                  
assert isEven(3) == false                                   
assert isEven.call(2) == true  

2.参数

2.1 一般参数

闭包参数和一般函数参数一样,任意类型,任意名字,还可以有默认值。

def closureWithOneArg = { str -> str.toUpperCase() }
assert closureWithOneArg('groovy') == 'GROOVY'

def closureWithOneArgAndExplicitType = { String str -> str.toUpperCase() }
assert closureWithOneArgAndExplicitType('groovy') == 'GROOVY'

def closureWithTwoArgs = { a,b -> a+b }
assert closureWithTwoArgs(1,2) == 3

def closureWithTwoArgsAndExplicitTypes = { int a, int b -> a+b }
assert closureWithTwoArgsAndExplicitTypes(1,2) == 3

def closureWithTwoArgsAndOptionalTypes = { a, int b -> a+b }
assert closureWithTwoArgsAndOptionalTypes(1,2) == 3

def closureWithTwoArgAndDefaultValue = { int a, int b=2 -> a+b }
assert closureWithTwoArgAndDefaultValue(1) == 3
2.2 隐藏参数

Groovy闭包不显式声明参数列表的话,默认使用it作为隐藏参数。

def greeting = { "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'
等价于
def greeting = { it -> "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'

如果希望强制不传参数,则使用如下方式:

def magicNumber = { -> 42 }

// this call will fail because the closure doesn't accept any argument
magicNumber(11)
2.3 不定数量参数

不定数量的参数和一般方法一样,直接举例子:

def concat1 = { String... args -> args.join('') }           
assert concat1('abc','def') == 'abcdef'                     
def concat2 = { String[] args -> args.join('') }            
assert concat2('abc', 'def') == 'abcdef'

def multiConcat = { int n, String... args ->                
    args.join('')*n
}
assert multiConcat(2, 'abc','def') == 'abcdefabcdef'

3.代理策略

3.1 闭包和lambda表达式

Groovy的闭包和Java8的lambda表达式是非常不同的,代理策略是一个表现和lambda表达式不同的关键概念。改变代理指向不同对象和不同的代理策略可以使Groovy构建优雅的DSL。

3.2 Owner,delegate,this

这三个概念是闭包的难点和核心,是闭包难理解的关键。

  • this 指向定义该闭包的(其实这里说的类只是为了排除闭包)
  • owner 指向定义该闭包的对象(这里的对象意思即:可能是类或闭包)
  • delegate 指向调用这个闭包的对象(默认策略下等同于owner) 或or properties are resolved whenever the receiver of the message is not defined(不会翻译这一句)。
3.2.1 this

其实上面对于概念的介绍已经算是清楚,下面主要看例子:

class Enclosing {
    void run() {
        def whatIsThisObject = { getThisObject() }       getThisObject()等同于得到闭包的this  或 等同于引用闭包外围的this变量
        assert whatIsThisObject() == this         这里证明闭包的this==其外围classthisthis应该是对象啊?)          
        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       这里即可排除指向闭包              
    }
}

利用闭包的这个特性的例子:

class Person {
    String name
    int age
    String toString() { "$name is $age years old" }

    String dump() {
        def cl = {
            String msg = this.toString()               
            println msg
            msg
        }
        cl()
    }
}
def p = new Person(name:'Janice', age:74)
assert p.dump() == 'Janice is 74 years old'
3.2.2 Owner

Owner和this非常像,区别是Owner指向离它最近的闭合对象,所以可能是class也可能是闭包。

class Enclosing {
    void run() {
        def whatIsOwnerMethod = { getOwner() }         getOwner()即得到闭包的Owner      
        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相同的       
    }
}
3.2.3 Delegate

Delegate可以用delegate属性取到,或用getDelegate方法,this和owner都是指向特定的范围,delegate是可以设定指向谁,这是groovy可以方便设计DSL的核心特性。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')        定义两个对象

def upperCasedName = { delegate.name.toUpperCase() }     定义一个闭包,注意并未定义在任一个类里面,而是单独在外面的

upperCasedName.delegate = p
assert upperCasedName() == 'NORMAN'       闭包的delegate指向p后,结果就是这样的
upperCasedName.delegate = t
assert upperCasedName() == 'TEAPOT'       闭包的delegate指向t后,结果变成这样

这可能有点像引用了一个变量而已,比如这样:

def target = p
def upperCasedNameUsingVar = { target.name.toUpperCase() }
assert upperCasedNameUsingVar() == 'NORMAN'

但是有两点不同
- target是闭包引用的外界的一个本地变量
- 对于delegate闭包可以直接显式调用,即:闭包名.delegate.变量 就行

3.2.4 代理策略

当一个闭包不定义在任何闭合环境内,把delegate指向谁,这个闭包中调用的属性或方法就指向delegate指向的对象。这是delegate在默认策略下的作用。

class Person {
    String name
}
def p = new Person(name:'Igor')   
def cl = { name.toUpperCase() }     这里name不知道到哪去找            
cl.delegate = p              但是这里delegate指向了p,name就去p里找                   
assert cl() == 'IGOR'

代理策略是指,找一个闭包内的属性或方法的方案,即,闭包内的属性或方法实际该指向谁。以下是这几种策略:
- Closure.OWNER_FIRST 这是默认策略,先在owner上找,没有的话,在delegate上找
- Closure.DELEGATE_FIRST 和上面相反
- Closure.OWNER_ONLY 只在owner上找,没有就报错
- Closure.DELEGATE_ONLY 和上面类似
- Closure.TO_SELF 如果你实现了自己的Closure类,这个就适用,这里执行你的自定义策略
下面举例子:

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'    默认策略,先找owner

如果改一下策略:

p.pretty.resolveStrategy = Closure.DELEGATE_FIRST
assert p.toString() == 'My name is Teapot'   先找delegate上的

resolveStrategy用来改策略的
下面再举例子:

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
}

4. 闭包在GString上的应用

先看例子:

def x = 1
def gs = "x = ${x}"
assert gs == 'x = 1'

x = 2
assert gs == 'x = 2'  这里返回false 

上面把x=1先赋值给了gs,从这里gs就生成了对象,这里就固定死了是1,如果x是个类对象,那当这个对象的toString方法返回值改变时,gs是会变的。即,gs生成时绑定了谁,那就是谁了,每次gs就读取这个对象的toString方法来赋值自己,这个对象内部可以变,但是gs不会再指向其他对象。所以如果想让gs跟着x变,就这么写 >x {x},后者相当于$x。而前者是一个闭包,相当于一个类对象。

def x = 1
def gs = "x = ${-> x}"   这里绑定了${-> x}这个对象,而不是x
assert gs == 'x = 1'

x = 2   ${-> x}这个对象的x属性变了
assert gs == 'x = 2'

再看两个对象的例子:

class Person {
    String name
    String toString() { name }          
}
def sam = new Person(name:'Sam')        
def lucy = new Person(name:'Lucy')      
def p = sam                             
def gs = "Name: ${p}"                   
assert gs == 'Name: Sam'                
p = lucy                      p指向了lucy,但是gs里绑定的是sam          
assert gs == 'Name: Sam'                
sam.name = 'Lucy'                       
assert gs == 'Name: Lucy'   

同样的,要想gs跟着p变:

class Person {
    String name
    String toString() { name }
}
def sam = new Person(name:'Sam')
def lucy = new Person(name:'Lucy')
def p = sam
// Create a GString with lazy evaluation of "p"
def gs = "Name: ${-> p}"       这里绑定了${-> p}这个对象,而不是p
assert gs == 'Name: Sam'
p = lucy                      ${-> p}这个对象的p属性变了
assert gs == 'Name: Lucy'

5. 闭包强制类型转换

待续。。

6. 函数式编程

6.1 Currying

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值