0%

Groovy之Closure闭包基本语法

这里对Groovy闭包的基本使用操作进行介绍

abstract.png

声明定义

Groovy采用 { 参数列表 -> 语句 } 的形式定义一个闭包。需要注意的是,如果闭包中没有通过->显式声明参数列表, Groovy会默认提供一个名为it的缺省参数。故如果期望声明一个无参数的闭包,需要显式添加->来声明空参数列表, 以避免Groovy提供默认参数it

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 ClosureDemo {
static void define() {
def range1 = 1..2
println("\n----------------- Test 1 -----------------")
range1.each { it -> println("Test 1: $it") }

println("\n----------------- Test 2 -----------------")
// 如果闭包没有通过->显式声明参数列表, Groovy会默认提供一个名为it的缺省参数
range1.each { println("Test 2: $it") }

println("\n----------------- Test 3 -----------------")
// 如果期望声明一个无参数的闭包,需要显式添加->来声明空参数列表, 以避免Groovy提供默认参数it
def closure3 = { -> println("Hello World") }
closure3.call()

println("\n----------------- Test 4 -----------------")
def closure1 = { num -> println("Test 4: $num") }
assert closure1 instanceof Closure
range1.each( closure1 )

println("\n----------------- Test 5 -----------------")
Closure closure2 = { println("Test 5: $it") }
assert closure2 instanceof Closure
range1.each( closure2 )
}
}

测试结果如下,符合预期

figure 1.jpeg

事实上,关于闭包的参数类型的数量,我们还可以通过闭包的getParameterTypes()方法进一步验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ClosureDemo {
static void other() {
// 获取闭包参数的数量
Closure closure1 = { print("Hello: $it") }
assert closure1.getParameterTypes().size() == 1

Closure closure2 = { print("Hello") }
assert closure2.getParameterTypes().size() == 1

Closure closure3 = { -> print("Hello") }
assert closure3.getParameterTypes().size() == 0

Closure closure4 = { x,y,z -> print("Hello") }
assert closure4.getParameterTypes().size() == 3
}
}

调用

由于Groovy本身就是一个对象实例,故自然是可以直接调用的。具体地,可通过添加括号、call方法进行传参、调用。同时闭包在定义过程中还支持带有缺省值的参数。这样在进行闭包调用时,对于未给定的参数值, 闭包会使用缺省值

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 ClosureDemo {
/**
* 直接调用闭包
*/
static void call() {
// 定义一个不需要参数的闭包
def closure1 = { "123" }
// 直接添加()调用该闭包
assert closure1() == "123"
// 通过call方法调用闭包
assert closure1.call() == "123"

// 定义一个需要两个参数的闭包
def closure2 = { x, y -> x+y }
assert closure2(1,3) == 4

// 闭包的参数类型是可选地
def closure3 = { String x, String y -> "${x}~~${y}" }
assert closure3.call('2','7') == "2~~7"

// 闭包支持带有缺省值的参数
def closure4 = { x, y=5 -> x*y }
assert closure4.call( 4,7 ) == 28
// 对于未给定的参数值, 闭包会使用缺省值
assert closure4.call( 4) == 20
}
}

返回值

对于闭包而言,闭包中如果未显式使用return语句, 返回最后一个表达式的结果;反之如果闭包中显式使用return语句, 返回指定值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ClosureDemo {    
static void returnResult() {
def closure1 = { ->
def list = []
list.add("Bob")
list.add("Aaron")
}
def result1 = closure1.call()
// 闭包中未显式使用return语句, 返回最后一个表达式的结果, 即list.add方法的返回值
assert result1 == true

def closure2 = { ->
def list = []
list.add("Bob")
list.add("Aaron")
return list
}
def result2 = closure2.call()
// 闭包中显式使用return语句, 返回指定值
assert result2 == ["Bob", "Aaron"]
}
}

方法闭包

事实上我们还可以通过 .&方法指针运算符 获取方法的引用作为闭包,即所谓的方法闭包。与此同时,方法闭包支持重载,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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class ClosureDemo {    
static void methodClosure() {
def list = ["Tony", "Amy", "civilization", "Aaron"]

StrRule strRule1 = new StrRule(4)
// 通过.&方法指针运算符, 获取 strRule1实例中validate方法的引用 作为闭包
// 该方法闭包等价于: { String str -> str.size() <= 4 }
MethodClosure closure1 = strRule1.&validate
assert closure1 instanceof Closure
def list1 = list.findAll( closure1 )
assert list1 == ["Tony", "Amy"]

StrRule strRule2 = new StrRule(6)
// 方法闭包 strRule2.&validate 等价于
// { String str -> str.size() <= 6 }
def list2 = list.findAll( strRule2.&validate )
assert list2 == ["Tony", "Amy", "Aaron"]

// 方法闭包支持重载, Groovy在运行时会选择合适的方法进行执行
Handler handler = new Handler()
def closure2 = handler.&handle
def result1 = closure2.call("Aaron")
assert result1 == "AARON"
def result2 = closure2.call(12)
assert result2 == 24
def result3 = closure2.call(3, 7)
assert result3 == 10
}
}

class StrRule{
int limit

StrRule(int limit) {
this.limit = limit
}

boolean validate(String str) {
return str.size() <= limit
}
}

class Handler{
String handle(String str) {
return str.toUpperCase()
}

int handle(int num) {
return 2 * num
}

int handle(int x, int y) {
return x + y
}
}

闭包三大属性:this、owner、delegate属性

Groovy的闭包中的三大属性:this、owner、delegate属性

闭包的this属性

闭包中的this指的是闭包定义时所处的类的实例。具体地:

  • 闭包定义在类中:闭包中的this 指的是 闭包定义处所在类的相应实例
  • 闭包定义在内部类中:闭包中的this 指的是 闭包定义处所在内部类的相应实例
  • 闭包A定义在另一个闭包B中:则闭包A中的this 指的依然是 闭包A所在类的相应实例

总而言之,this指的是距离闭包定义处最近类相应的实例。测试代码如下所示

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
class ThisInClosure {
static void main(String[] args) {
Employee employee = new Employee("Aaron", 18)
assert employee.getInfo() == "[Employee Info]: <name> : Aaron, <age>: 18"
assert employee.getInfo2() == "[Employee Info]: <name> : Aaron, <age>: 18"

/*************** 闭包定义在类中 ***************/
Closure closure1 = employee.test1()
// 闭包中的this 指的是 闭包定义处所在类的相应实例
assert closure1.call() == employee

Closure closure2 = employee.test2()
// 闭包中的getThisObject()方法 作用于this类似
assert closure2.call() == employee

/*************** 闭包定义在内部类中 ***************/
Employee.Inner inner = employee.getInnerInstance()
Closure closure3 = inner.test3()
// 闭包如果定义在内部类中, 则 闭包中的this 指的是 相应的内部类实例
assert closure3.call() != employee
assert closure3.call() == inner

/*************** 闭包嵌套定义在另一个闭包中 ***************/
Closure closure4 = employee.test4()
// 嵌套定义的闭包, 则 嵌套闭包中的this 指的依然是 嵌套闭包所在类的相应实例
assert closure4.call() == employee
}
}

class Employee {
String name

int age

Employee(String name, int age) {
this.name = name
this.age = age
}

String getInfo() {
return "[Employee Info]: <name> : $name, <age>: $age"
}

String getInfo2() {
// 同Java一样, Groovy this 表示 调用该方法的对象实例
return this.getInfo()
}

def test1() {
// 闭包定义处
def closure = { this }
return closure
}

def test2() {
// 闭包中的 getThisObject()方法 和 this 是完全等价的, 后者是前者的快捷方式
def closure = { getThisObject() }
return closure
}

def test4() {
def closure = { ->
// 嵌套定义一个新闭包nestedClosure, 并在该闭包中使用this
def nestedClosure = { -> this }
// 调用该嵌套闭包, 并将结果返回
def result = nestedClosure.call()
return result
}
return closure
}

/**
* 获取内部类实例
* @return
*/
def getInnerInstance() {
return new Inner()
}

/**
* 内部类
*/
class Inner{
def test3() {
// 闭包定义处
def closure = { this }
return closure
}
}
}

闭包的owner属性

闭包中的owner指的是闭包定义时所处的封闭对象的实例。这里的封闭对象可以是一个类,也可以是一个闭包。具体地:

  • 闭包定义在类中:闭包中的owner 指的是 闭包定义处所在类的相应实例
  • 闭包定义在内部类中:闭包中的owner 指的是 闭包定义处所在内部类的相应实例
  • 闭包A定义在另一个闭包B中:则闭包A中的owner 指的是 闭包A所在闭包B的相应实例

总而言之,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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
class OwnerInClosure {
static void main(String[] args){
Person person = new Person("Bob", 25)

/*************** 闭包定义在类中 ***************/
Closure closure1 = person.test1()
// 闭包中的owner 指的是 闭包定义处所在类的相应实例
assert closure1.call() == person

Closure closure2 = person.test2()
// 闭包中的getOwner()方法 作用于owner类似
assert closure2.call() == person

/*************** 闭包定义在内部类中 ***************/
Person.Inner inner = person.getInnerInstance()
Closure closure3 = inner.test3()
// 闭包如果定义在内部类中, 则 闭包中的owner 指的是 相应的内部类实例
assert closure3.call() != person
assert closure3.call() == inner

/*************** 闭包嵌套定义在另一个闭包中 ***************/
Closure closure4 = person.test4()
// 嵌套定义的闭包, 则 嵌套闭包中的owner 指的是 嵌套闭包所在的外部闭包实例, 见test4方法内的(1)处
assert closure4.call() != person
assert closure4.call() == closure4
}
}

class Person {
String name

int age

Person(String name, int age) {
this.name = name
this.age = age
}

def test1() {
// 闭包定义处
def closure = { owner }
return closure
}

def test2() {
// 闭包中的 getOwner()方法 和 owner 是完全等价的, 后者是前者的快捷方式
def closure = { getOwner() }
return closure
}

def test4() {
def closure = { ->
// 嵌套定义一个新闭包nestedClosure, 并在该闭包中使用owner
def nestedClosure = { -> owner }
// 调用该嵌套闭包, 并将结果返回
def result = nestedClosure.call()
return result
}

// 嵌套闭包中的owner 指的是 嵌套闭包定义处的外部闭包实例
assert closure() == closure // (1)

return closure
}

/**
* 获取内部类实例
* @return
*/
def getInnerInstance() {
return new Inner()
}

/**
* 内部类
*/
class Inner{
def test3() {
// 闭包定义处
def closure = { owner }
return closure
}
}
}

闭包的delegate属性

上面介绍闭包中的this、owner,其所指向的对象都是确定的。即开发者不可以进行修改,使其指向别的对象。为此闭包提供了一个delegate。 默认情况下, 闭包的 delegate 被设置为 owner。故当一个闭包被定义在类、内部类、另一个闭包中时,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
34
35
36
37
38
39
class DelegateInClosure {
static void basic(){
Staff staff = new Staff("Amy", 38)

// 默认情况下, 闭包的 delegate 被设置为 owner
// 故当一个闭包被定义在类、内部类、嵌套的闭包时
// delegate所指向的对象与owner的行为完全一致

Closure closure1 = staff.test1()
assert closure1.call() == staff

Closure closure2 = staff.test2()
// 闭包中的getDelegate()方法 作用于delegate类似
assert closure2.call() == staff
}
}

class Staff {
String name

int age

Staff(String name, int age) {
this.name = name
this.age = age
}

def test1() {
// 闭包定义处
def closure = { delegate }
return closure
}

def test2() {
// 闭包中的 getDelegate()方法 和 delegate 是完全等价的, 后者是前者的快捷方式
def closure = { getDelegate() }
return closure
}
}

由于闭包的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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class DelegateInClosure {
static void modifyDelegate() {
Staff staff1 = new Staff("Tony", 22)
Animal animal1 = new Animal("Tom", "Cat")

// 定义一个闭包
def closure = { ->
// 获取delegate所指向对象的name字段
def nameStr = delegate.name
// 将nameStr全部转换为大写
nameStr.toUpperCase()
}

// 如前所述, 闭包的delegate属性默认指向其定义时所在类的相应实例
// 由于这里闭包定义在静态方法中, 故其指向的是Class对象
assert closure.delegate == DelegateInClosure.class

// 修改闭包的delegate属性
closure.delegate = staff1
assert closure.call() == "TONY"

// 修改闭包的delegate属性
closure.delegate = animal1
assert closure.call() == "TOM"

// 如果不借助闭包的delegate属性, 而通过本地变量进行实现的话, 则可能是这样
def temp = staff1
def closure2 = { temp.name.toUpperCase() }
assert closure2.call() == "TONY"
temp = animal1
assert closure2() == "TOM"
}
}

class Animal{
String name

String type

Animal(String name, String type) {
this.name = name
this.type = type
}
}

参考文献

  1. Groovy In Action · 2nd Edition Dierk König、Guillaume Laforge著
请我喝杯咖啡捏~

欢迎关注我的微信公众号:青灯抽丝