groovy闭包

闭包定义

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

彼得·兰丁在1964年将术语“闭包”定义为一种包含环境成分和控制成分的实体。用来指代某些其开放绑定(自由变量)已经由其语法环境完成闭合(或者绑定)的lambda表达式,从而形成了闭合的表达式,或称闭包。

闭包只是在形式和表现上像函数,但实际上不是函数。函数是一些可执行的代码,这些代码在函数被定义后就确定了,不会在执行时发生变化,所以一个函数只有一个实例。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。所谓引用环境是指在程序执行中的某个点所有处于活跃状态的约束所组成的集合。其中的约束是指一个变量的名字和其所代表的对象之间的联系。

在函数定义时捕获当时的引用环境,并与函数代码组合成一个整体。当把这个整体当作函数调用时,先把其中的引用环境覆盖到当前的引用环境上,然后执行具体代码,并在调用结束后恢复原来的引用环境。这样就保证了函数定义和执行时的引用环境是相同的。这种由引用环境与函数代码组成的实体就是闭包。

自由变量是指除局部变量以外的变量。

Groovy闭包定义

Groovy中的闭包是一个开放的、匿名代码块,它可以接收参数,定义返回值,也可以将闭包复制给变量,闭包还可以引用定义在其周围范围(in its surrounding scope)中的变量。与闭包的正式定义相反,Groovy语言中的闭包还可以包含定义在其周围范围之外(outside of its surrounding scope)的自由变量。

Groovy闭包语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{ [closureParameters ->] statements}

// examples
{ item++ } // 引用变量item
// 默认参数it
// 如果闭包执行时未指定参数,则it为null,类型class org.codehaus.groovy.runtime.NullObject
{ println it}

//显示指定参数
{name -> println name}

//指定多个具有类型的参数
def body = {String name, int age -> println "name: ${name}, age:${age}"}

//闭包是groovy.lang.Closure类型的实例
assert body instanceof Closure

//参数可以指定默认值
def sum = { int a, int b=2 -> a+b }

//可以指定闭包的返回值类型
Closure<Boolean> isTextFile = { File it ->
it.name.endsWith('.txt')
}

//如果要明确指定闭包不包含参数,需要显示的指定空参数列表
def magicNumber = { -> 42 }
// this call will fail because the closure doesn't accept any argument
magicNumber(11)

//闭包的可以指定最后一个参数的长度是可变的或定义为数组
def concat1 = {String... args -> args.join('')}
assert concat1('abc', 'def') == 'abcdef'
def concat2 = {String[] args -> args.join('')}
assert concat2('aaa', 'bbb') == 'aaabbb'
//闭包的执行也可以使用call()
def multiConcat = {int n, String... args -> args.join('')*n}
assert multiConcat.call(3, 'ab', 'cd') == 'abcdabcdabcd'

closureParameters是可选的逗号分隔的参数列表,参数可以指定类型(typed)也可不指定类型(untyped)。如果指定了参数,参数后面必须有 ->,用于分割参数和闭包内语句。

闭包执行时,总是会有返回值。

Groovy闭包委托策略(Delegation strategy)

委托(Delegation)是Groovy闭包中的一个关键特性(key concept),闭包委托策略的可修改使得在Groovy中设计漂亮的领域特定语言(dsl, domain specific language)成为可能。

this、owner、delegate

闭包内有三个内置对象:

  • this 对应于定义闭包的封闭类(the enclosing class where the closure is defined),可以在闭包内通过getThisObject()获取
  • owner 对应于定义闭包的封闭对象(the enclosing object where the closure is defined),可以是类也可以是闭包,可以在闭包内通过getOwner()获取
  • delegate 对应于一个第三方对象(where methods calls or properties are resolved whenever the receiver of the message is not defined),可以在闭包内通过(getDelegate())获取

通过这三个内置对象,闭包可以调用对应对象的属性和方法。

this

对应于定义闭包的封闭类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 在TestThis类中定义闭包body,并返回闭包的this对象
class TestThis{
void run() {
def body = {getThisObject()}
// 调用闭包会返回TestThis类的实例
assert body() == this
// 在闭包中直接使用this对象与调用getThisObject等价
def body2 = { this }
assert body2 == this
}
}

class ClosureTest {
static void main(String... args) {
def body = { this }
println body() == this // true
println this // class ClosureTest
println(this.getClass().toString()) // class java.lang.Class
println(body.getClass().toString()) // class ClosureTest$_main_closure1
println body instanceof Closure // true
}
}
如果闭包在内部类中定义,那么闭包中的 this 对象返回的是内部类的实例对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ClosureTest {
class InnerClass {
def body = { this }
}
void run(){
InnerClass inner = new InnerClass()
println inner == inner.body() // true
println this == inner.body() // false
println this.getClass().toString() // class ClosureTest
println inner.getClass().toString() // class ClosureTest$InnerClass
println inner.body.getClass().toString() // class ClosureTest$InnerClass$_closure1
}

static void main(String... args) {
new ClosureTest().run()
}
}
如果闭包A嵌入在类的某个闭包B中时,闭包A中的 this 表示的仍然是类的实例对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ClosureTest {
void run(){
def body_b = {
def body_a = { this }
body_a()
}
println body_b() // ClosureTest@25748410
println this // ClosureTest@25748410
println body_b() == this // true
println this.getClass().toString() // class ClosureTest
}

static void main(String... args) {
new ClosureTest().run()
}
}

owner

返回闭包定义所在的封闭对象,可以是类也可以是闭包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 普通类
class ClosureTest {
void run(){
def body_b = { owner }
println body_b() // ClosureTest@55b5f5d2
println body_b // ClosureTest$_run_closure1@5bfa8cc5
println this // ClosureTest@55b5f5d2
}

static void main(String... args) {
new ClosureTest().run()
}
}

// 内部类
class ClosureTest {
class InnerClass {
def body = { owner }
}

void run() {
def inner = new InnerClass()
println inner // ClosureTest$InnerClass@553f1d75
println inner.body() // ClosureTest$InnerClass@553f1d75
println this // ClosureTest@47404bea
}

static void main(String... args) {
new ClosureTest().run()
}
}

// 嵌套闭包
class ClosureTest {
void run(){
def body_b = {
def body_a = { owner }
println body_a // ClosureTest$_run_closure1$_closure2@5bfa8cc5
println body_a() // ClosureTest$_run_closure1@16ecee1
body_a()
}
println body_b() // ClosureTest$_run_closure1@16ecee1
println body_b // ClosureTest$_run_closure1@16ecee1
}

static void main(String... args) {
new ClosureTest().run()
}
}

delegate

委托是Groovy语言能够构建DSL的关键特性(It is a powerful concept for building domain specific languages in groovy)。delegate是一个用户自定义的对象。

默认情况下, delegate 等同于 owner
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class ClosureTest {
class InnerClass {
def body = { delegate }
}

void run() {
def inner = new InnerClass()
println inner // ClosureTest$InnerClass@4f071df8
println inner.body() // ClosureTest$InnerClass@4f071df8
println this // ClosureTest@29e6eb25

def body_b = {
def body_a = { delegate }
body_a()
}
println body_b() // ClosureTest$_run$_closure1@38be305c
println body_b // ClosureTest$_run$_closure1@38be305c

def body_c = { delegate }
println body_c() // ClosureTest@29e6eb25
println body_c // ClosureTest$_run$_closure2@5ed731d0
}

static void main(String... args) {
new ClosureTest().run()
}
}
闭包中的delegate属性可以被修改为任何对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class ClosureTest {
class Person {
String name
}

class Animal {
String name
}

void run() {
def p = new Person(name: 'john')
def a = new Animal(name: 'panda')
def nameToUpper = { delegate.name.toUpperCase() }
nameToUpper.delegate = p
println nameToUpper() // JOHN
nameToUpper.delegate = a
println nameToUpper() // PANDA
println p.name // john
println a.name // panda

// nameToUpper_use_var引用外部的局部变量p,能够达成和前面使用delegate一样的效果
// 但是委托可以透明的使用,即在闭包中不再显式的采用 delegate. 前缀引用属性或方法,详情见下节委托策略
def nameToUpper_use_var = { p.name.toUpperCase() }
println nameToUpper_use_var() // JOHN
}

static void main(String... args) {
new ClosureTest().run()
}
}

闭包委托策略

在闭包中,如果访问闭包内部未定义的属性或方法时,会涉及到委托策略,委托策略分为下面几种:

  • Closure.OWNER_FIRST: 默认策略。优先在owner中查找,如果没有找到则在delegate中查找
  • Closure.DELEGATE_FIRST: 优先在delegate中查找, 如果没有找到则在owner中查找
  • Closure.OWNER_ONLY: 忽略delegate,只在owner中查找
  • Closure.DELEGATE_ONLY: 忽略owner, 只在delegate中查找
  • Closure.TO_SELF: 只有在实现自己的闭包子类时才有意义。在需要高级元编程(meta-programming)技术,希望实现自定义的解析策略: 属性或方法的解析既不使用owner也不使用delegate,only on the closure class itself.

DELEGATE_FIRST 与 OWNER_FIRST

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class ClosureTest {
class Person {
String name
def output = { "My name is ${name}" }
String outputstring() {
output()
}
}

class Animal {
String name
}

void run() {
def p = new Person(name: 'john')
def a = new Animal(name: 'panda')
println p.output.owner // ClosureTest$Person@3234f74a
println p.output.delegate // ClosureTest$Person@3234f74a
// 默认委托策略为owner_first所以从p中查找name属性
println p.outputstring() // My name is john
p.output.delegate = a
println p.output.owner // ClosureTest$Person@3234f74a
println p.output.delegate // ClosureTest$Animal@65aa6596
// 只修改了delegate属性,但策略没变,仍然从p中查找name属性
println p.outputstring() // My name is john
// 修改委托策略为delegate_first
p.output.resolveStrategy = Closure.DELEGATE_FIRST
// 从a中查找name属性
println p.outputstring() // My name is panda
}

static void main(String... args) {
new ClosureTest().run()
}
}

DELEGATE_FIRST 与 DELEGATE_OWNER

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class ClosureTest {
class Person {
String name
int age
def output = { "${name} age is ${age}" }
String outputstring() {
output()
}
}

class Animal {
String name
}

void run() {
def p = new Person(name: 'john', age: 18)
def a = new Animal(name: 'panda')
println p.outputstring() // john age is 18
p.output.delegate = a
println p.outputstring() // john age is 18
p.output.resolveStrategy = Closure.DELEGATE_ONLY
p.output.delegate = p
println p.outputstring() // john age is 18
p.output.delegate = a
// exception:
// groovy.lang.MissingPropertyException: No such property: age for class: ClosureTest
println p.outputstring()
}

static void main(String... args) {
new ClosureTest().run()
}
}

闭包委托策略具有传递性

闭包A内嵌与闭包B时,如果闭包A中的某个属性在闭包B中没有解析到会继续向闭包B的ownerdelegate中查找。具体是向闭包的B的owner还是delegate中查找,由闭包B的委托策略决定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class ClosureTest {
class Person {
String name
int age
def outerClosure = {
// outerClosure为innerClosure的owner
def name = "outer_${name}"
def innerClosure = {
// innerClosure闭包中的name和age属性都不在闭包内定义
// 默认从innerClosure.owner中查找
// outerClousre.name
// p.age
"${name}'s age is ${age}"
}
println innerClosure.owner // ClosureTest$Person$_closure1@26a4842b
println innerClosure.delegate == innerClosure.owner // ture
innerClosure()
}
}

void run() {
def p = new Person(name: 'john', age: 18)
println p // ClosureTest$Person@5ed731d0
// p.outerClosure的owner为 p
println p.outerClosure // ClosureTest$Person$_closure1@26a4842b
println p.outerClosure.delegate == p // true
println p.outerClosure.owner == p // true
println p.outerClosure() // outer_john's age is 18
}

static void main(String... args) {
new ClosureTest().run()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class ClosureTest {
class Person {
String name
int age
def outerClosure = {
def name = "outer_${name}"
def innerClosure = {
"${name}'s age is ${age}"
}
innerClosure()
}
}

void run() {
def p = new Person(name: 'john', age: 18)
def p2 = new Person(name: 'jessica', age: 3)
println p.outerClosure() // outer_john's age is 18
p.outerClosure.resolveStrategy = Closure.DELEGATE_FIRST
p.outerClosure.delegate = p2
println p.outerClosure() // outer_jessica's age is 3
}

static void main(String... args) {
new ClosureTest().run()
}
}

闭包策略在Jenkins pipeline中的典型应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// vars/abc.groovy
def call(body) {
def config = [:]
// 修改body闭包的委托策略和delegate属性
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = config

// 闭包执行时,闭包中读取未定义的属性时都会从config中获取
// 闭包中设置未定义的属性时都也会设置到config中
body()
// 后续就可以通过config.branch访问Jenkinsfile中定义并传给abc.groovy的值
println config // [branch: 'master']
}

// Jenkinsfile
abc {
// 通常设置某些属性值,可以实现传参效果
branch = 'master'
}

参考资料

维基百科-闭包

闭包的概念、形式与应用 (IBM DeveloperWorks)

Groovy-Closure