Groovy Closure(闭包)

1. 语法

1.1. 定义一个闭包

闭包是Groovy中非常重要的一个数据类型或者说一种概念。闭包是一种数据类型,它代表了一段可执行的代码

闭包的语法定义: { [closureParameters -> ] statements }

其中[closureParameters->]部分是一个可选的以逗号分隔的参数列表

statements可以为空,一行或者多行代码。

当参数列表确定时,->是必须的,他负责把参数和闭包的代码块分开,代码块可以由一行或者多行语句组成

一些闭包常用的定义形式:

{ item++ }                                          

{ -> item++ }                                       

{ println it }                                      

{ it -> println it }                                

{ name -> println name }                            

{ String x, int y ->                                
    println "hey ${x} the value is ${y}"
}

{ reader ->                                         
    def line = reader.readLine()
    line.trim()
}

如果闭包没有定义参数的话,则隐含有一个参数,这个参数的名字交it,和this作用类似,it代表闭包的参数。

1.2. 闭包作为一个对象

闭包实质上是一个groovy.lang.Closure类的实例,虽然他是一个代码块,但是他可以为任何一个变量或者字段赋值,闭包中可以包含代码的逻辑,闭包中的最后一行语句,表示该闭包的返回值,不论该语句是否有return关键字

//可以将闭包分配给一个变量,这个变量就是groovy.lang.Closure的一个实例
def listener = { e -> println "Clicked on $e.source" }      
assert listener instanceof Closure

//如果不使用def,可以将一个闭包分配给一个类型为groovy.lang.Closure的变量
Closure callback = { println 'Done!' }     

//可以通过使用groovy.lang.Closure的泛型来指定闭包的返回类型
Closure isTextFile = {
    File it -> it.name.endsWith('.txt')                     
}

1.3. 调用闭包

闭包作为一个匿名的代码块,可以像方法那样被调用,如果定义了一个不需要参数的闭包:

def code = { 123 }

闭包内的代码只有在闭包被调用时才会执行,调用可以像常规方法那样来完成

assert code() == 123

或者,可以显式的调用call方法

assert code.call() = 123

example:

def isOdd = { int i -> i%2 != 0 }                           
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

闭包提供了询问自己参数个数的方法,无论在闭包内或者闭包外

c= {x,y,z-> getMaximumNumberOfParameters() }  
assert c.getMaximumNumberOfParameters() == 3  
assert c(4,5,6) == 3  

2.2. 隐式参数

当一个闭包没有明确定义一个参数列表(使用 - >)时,闭包总是定义一个隐式参数,并将其命名为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)

闭包的一些快捷写法,当闭包作为闭包或方法的最后一个参数。可以将闭包从参数圆括号中提取出来接在最后,如果闭包是唯一的一个参数,则闭包或方法参数所在的圆括号也可以省略。对于有多个闭包参数的,只要是在参数声明最后的,均可以按上述方式省略。见代码:

def runTwice = { a, c -> c(c(a)) }  
assert runTwice( 5, {it * 3} ) == 45 //usual syntax  
assert runTwice( 5 ){it * 3} == 45  
    //when closure is last param, can put it after the param list  
  
def runTwiceAndConcat = { c -> c() + c() }  
assert runTwiceAndConcat( { 'plate' } ) == 'plateplate' //usual syntax  
assert runTwiceAndConcat(){ 'bowl' } == 'bowlbowl' //shortcut form  
assert runTwiceAndConcat{ 'mug' } == 'mugmug'  
    //can skip parens altogether if closure is only param  
  
def runTwoClosures = { a, c1, c2 -> c1(c2(a)) }  
    //when more than one closure as last params  
assert runTwoClosures( 5, {it*3}, {it*4} ) == 60 //usual syntax  
assert runTwoClosures( 5 ){it*3}{it*4} == 60 //shortcut form  

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. This owner delegate

Groovy的闭包比Java的Lambda表达式功能更强大。原因就是Groovy闭包可以修改委托对象和委托策略。这样Groovy就可以实现非常优美的领域描述语言(DSL)了。Gradle就是一个鲜明的例子

Groovy闭包有三个相关的对象

  • this 即闭包定义所在的类。
  • owner 即闭包定义所在的对象或闭包。
  • delegate 即闭包中引用的第三方对象。
3.1.1 this

this代表闭包定义所在的类

在闭包中,调用getThisObject将返回闭包定义的闭包类,和显式的调用this一样

class Enclosing {
    void run() {
        def whatIsThisObject = { getThisObject() }          
        assert whatIsThisObject() == this                   
        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 close = {
            def cl = { this }                               
            cl()
        }
        assert nestedClosures() == this                     
    }
}

在Inner中调用this 返回的是Inner这个类,而不是外层的EnclosedInInnerClass

在NestedClosures类中,cl定义在闭包close中,调用this返回最近的外层类NestedClosures,而不是闭包close

可以像下面这样调用封闭类的方法

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.1.2 Owner

Owner代表闭包定义所在的对象或闭包

class Enclosing {
    void run() {
        def whatIsOwnerMethod = { getOwner() }               
        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            
    }
}
3.1.3 Delegate of a closure

可以使用delegate属性或者调用getDelegate方法来访问闭包中的delegate,delegate在Groovy中是一个很重要的概念,delegate需要我们手动指定

class Enclosing {
    void run() {
        def cl = { getDelegate() }                          
        def cl2 = { delegate }                              
        assert cl() == cl2()                                
        assert cl() == this                                 
        def enclosed = {
            { -> delegate }.call()                          
        }
        assert enclosed() == enclosed                       
    }
}

闭包的delegate可以被改变成任何对象。 我们通过创建两个不是彼此的子类的类来说明这一点,但都定义了一个名为name的属性:

class Person {
    String name
}
class Thing {
    String name
}

def p = new Person(name: 'Norman')
def t = new Thing(name: 'Teapot')

然后定义一个闭包通过delegate来调用name属性

def upperCasedName = { delegate.name.toUpperCase() }

然后通过更改闭包的委托,可以看到目标对象将会改变

upperCasedName.delegate = p
assert upperCasedName() == 'NORMAN'
upperCasedName.delegate = t
assert upperCasedName() == 'TEAPOT'

上述行为与在闭包的词法范围中定义目标变量并没有区别

def target = p
def upperCasedNameUsingVar = { target.name.toUpperCase() }
assert upperCasedNameUsingVar() == 'NORMAN'
3.1.4 Delegation strategy(委托策略)

无论什么时候,在闭包中,访问某个属性时都没有明确地设置接收者对象,那么就会调用一个委托策略:

class Person {
    String name
}
def p = new Person(name:'Igor')
def cl = { name.toUpperCase() }                 
cl.delegate = p

//name property be resolved transparently on the delegate object!
assert cl() == 'IGOR'  

调用name属性并没有执行是谁的name属性,然后把闭包cl的delegate设置为p

这里没有显式的给delegate设置一个接收者,但是能成功访问到name属性 因为相应的属性解析策略:

  • Closure.OWNER_FIRST,默认策略,首先从owner上寻找属性或方法,找不到则在delegate上寻找。
  • Closure.DELEGATE_FIRST,和上面相反。首先从delegate上寻找属性或者方法
  • Closure.OWNER_ONLY,只在owner上寻找,delegate被忽略。
  • Closure.DELEGATE_ONLY,和上面相反。只在delegate上寻找,owner被忽略。
  • Closure.TO_SELF,高级选项,让开发者自定义策略。

看下面的例子:

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'  

Person和Thing都定义了name属性,使用默认策略,首先会在Owner上寻找,所以即使我们把delegate换成t,输出结果还是一样的

下面改变一下委托策略

p.pretty.resolveStrategy = Closure.DELEGATE_FIRST
assert p.toString() == 'My name is Teapot'

把委托策略改成Closure.DELEGATE_FIRST,则会首先去delegate寻找name属性,如果没有找到,再去owner上寻找,但是delegate有name的定义,所以输出结果为“My name is Teapot”

delegate first vs delegate only (owner firstvsowner only)

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
}

only时,在对应的对象上找不到时,不会接着去别处找,而是抛出一个异常

4.函数式编程

待...

参考:

Groovy基础——Closure(闭包)详解

Closures

你可能感兴趣的:(Groovy Closure(闭包))