1. 概述
在本入门教程中,我们将深入探讨 Groovy 中的一个核心特性:闭包(Closure)。闭包是一种匿名代码块,在 Groovy 中是 Closure
类的实例。
很多语言都支持闭包的概念,比如 JavaScript 和 Python,但不同语言中闭包的特性和行为会有所差异。
本文将带你了解 Groovy 闭包的关键特性,并通过示例展示其常见用法。
2. 什么是闭包?
简单来说,闭包是一个匿名的代码块。在 Groovy 中,它是 Closure
类的实例。闭包可以接受 0 个或多个参数,并且总是返回一个值。
✅ 闭包的一个强大之处在于:它可以访问其作用域之外的变量,并在执行时使用这些外部变量(以及自己的局部变量)。
此外,闭包可以赋值给变量,也可以作为参数传递给方法。因此,闭包支持延迟执行的功能。
3. 闭包的声明
Groovy 闭包的结构为:参数列表 + 箭头 ->
+ 执行代码块。 参数是可选的;如果提供了多个参数,需要用逗号分隔。
3.1 基本声明
def printWelcome = {
println "Welcome to Closures!"
}
这个闭包 printWelcome
在被调用时会打印一句话。我们再来看一个带参数的闭包示例:
def print = { name ->
println name
}
这个 print
闭包接受一个参数 name
,并在调用时将其打印出来。
虽然闭包的定义看起来很像方法,但我们来对比一下:
def formatToLowerCase(name) {
return name.toLowerCase()
}
def formatToLowerCaseClosure = { name ->
return name.toLowerCase()
}
这两个写法功能相似,但闭包和方法之间存在一些细微差异,我们将在后面详细讨论。
3.2 闭包的执行方式
闭包有两种执行方式:
- 像普通方法一样直接调用
- 使用
call()
方法
示例:
print("Hello! Closure")
formatToLowerCaseClosure("Hello! Closure")
或者使用 call()
:
print.call("Hello! Closure")
formatToLowerCaseClosure.call("Hello! Closure")
4. 参数详解
Groovy 闭包的参数机制与普通方法类似,但也有一些特殊之处。
4.1 隐式参数 it
对于只有一个参数的闭包,我们可以省略参数声明,Groovy 会默认使用一个名为 it
的隐式参数:
def greet = {
return "Hello! ${it}"
}
assert greet("Alex") == "Hello! Alex"
4.2 多参数闭包
def multiply = { x, y ->
return x*y
}
assert multiply(2, 4) == 8
4.3 参数类型声明
闭包参数可以指定类型,如下所示:
def calculate = {int x, int y, String operation ->
def result = 0
switch(operation) {
case "ADD":
result = x+y
break
case "SUB":
result = x-y
break
case "MUL":
result = x*y
break
case "DIV":
result = x/y
break
}
return result
}
assert calculate(12, 4, "ADD") == 16
assert calculate(43, 8, "DIV") == 5.375
4.4 可变参数(Varargs)
闭包也支持可变参数,写法和方法类似:
def addAll = { int... args ->
return args.sum()
}
assert addAll(12, 10, 14) == 36
5. 闭包作为方法参数
我们可以将闭包作为参数传递给普通方法,从而实现行为的定制化。
举个例子:计算规则图形的体积。体积 = 面积 × 高度,但面积的计算方式因形状而异。
我们可以写一个通用的 volume
方法,将面积计算逻辑通过闭包传入:
def volume(Closure areaCalculator, int... dimensions) {
if(dimensions.size() == 3) {
return areaCalculator(dimensions[0], dimensions[1]) * dimensions[2]
} else if(dimensions.size() == 2) {
return areaCalculator(dimensions[0]) * dimensions[1]
} else if(dimensions.size() == 1) {
return areaCalculator(dimensions[0]) * dimensions[0]
}
}
assert volume({ l, b -> return l*b }, 12, 6, 10) == 720
再计算一个圆锥的体积:
assert volume({ radius -> return Math.PI*radius*radius/3 }, 5, 10) == Math.PI * 250/3
6. 嵌套闭包
闭包中可以定义和调用另一个闭包:
def calculate = {int x, int y, String operation ->
def log = {
println "Performing $it"
}
def result = 0
switch(operation) {
case "ADD":
log("Addition")
result = x+y
break
case "SUB":
log("Subtraction")
result = x-y
break
case "MUL":
log("Multiplication")
result = x*y
break
case "DIV":
log("Division")
result = x/y
break
}
return result
}
7. 字符串的惰性求值
Groovy 中的字符串通常在创建时就被插值:
def name = "Samwell"
def welcomeMsg = "Welcome! $name"
assert welcomeMsg == "Welcome! Samwell"
即使之后修改 name
的值,字符串也不会更新:
name = "Tarly"
assert welcomeMsg != "Welcome! Tarly"
但通过闭包插值,我们可以实现惰性求值,让字符串根据当前变量值重新计算:
def fullName = "Tarly Samson"
def greetStr = "Hello! ${-> fullName}"
assert greetStr == "Hello! Tarly Samson"
修改变量后,字符串也会更新:
fullName = "Jon Smith"
assert greetStr == "Hello! Jon Smith"
8. 闭包在集合中的应用
Groovy 的集合类大量使用闭包来实现各种操作。
例如,遍历列表并打印:
def list = [10, 11, 12, 13, 14, true, false, "BUNTHER"]
list.each {
println it
}
assert [13, 14] == list.findAll{ it instanceof Integer && it >= 13 }
还可以从 Map 构造新列表:
def map = [1:10, 2:30, 4:5]
assert [10, 60, 20] == map.collect{it.key * it.value}
9. 闭包 vs 方法
虽然闭包在语法上与方法相似,但它们有本质区别:
- ✅ 闭包可以作为参数传递
- ✅ 一元闭包支持隐式参数
it
- ✅ 闭包可以赋值给变量并延迟执行
- ✅ 闭包的返回类型是运行时确定的
- ✅ 闭包支持嵌套定义
- ✅ 闭包总是返回一个值
因此,闭包比普通方法更加灵活,是 Groovy 的一大利器。
10. 总结
本文详细介绍了 Groovy 中闭包的定义、使用方式及其与方法的区别。
闭包提供了一种强大的机制,可以在对象和方法中注入功能,实现延迟执行,极大地提升了代码的灵活性和可读性。
一如既往,本文的代码和单元测试可以在 GitHub 仓库 中找到。