ZetCode

Groovy 闭包

最后修改日期:2025 年 3 月 22 日

Groovy 中的闭包是动态的、可重用的代码块,你可以将它们分配给变量、传递或按需执行。它们比 Java 的 Lambda 表达式更具通用性,将函数式编程与 Groovy 的富有表现力的语法融为一体。本教程通过实际的、真实世界的示例,探讨如何创建和使用闭包。

定义闭包

闭包是用花括号 {} 雕刻而成的,可以选择性地接受参数并返回值。作为一等公民,闭包可以像任何变量一样存储、共享和调用,从而解锁了灵活逻辑的世界。

SimpleClosure.groovy
def welcome = { name -> "Welcome, $name, to our site!" }

println welcome("Alice")

这里,welcome 是一个闭包,它通过 name 向用户致以问候,并使用 Groovy 的字符串插值来生成一个完美的问候语。想象一下在 Web 应用程序中,用最少的麻烦为每个访问者个性化登录横幅。

将闭包作为方法参数

当将闭包传递给方法时,它们会大放异彩,将自定义行为注入可重用的函数中。这使得你的代码具有适应性,可以让你在不重写核心结构的情况下交换逻辑。

ClosureAsArgument.groovy
def scheduleTasks(int count, Closure task) {
    count.times { task(it + 1) }
}

scheduleTasks(3) { taskNum ->
    println "Task $taskNum scheduled for today"
}

scheduleTasks 接受一个计数和一个闭包,并通过 times 对每次迭代执行该闭包。这里,它安排了编号的任务,模仿了一个待办事项应用程序,其中闭包定义了每个任务的内容——灵活而直接。

具有多个参数的闭包

闭包可以处理多个参数,这对于需要一个以上输入的运算非常有用,从计算到数据转换。

MultiParamClosure.groovy
def calculateTax = { price, rate -> price * (1 + rate) }

println calculateTax(100, 0.08)

calculateTax 函数计算带税的总价,它接受 pricerate。这可以为电子商务结账提供支持,快速计算税后价格,而不会使代码因完整的函数定义而混乱。

将闭包作为返回值

方法可以创建并返回闭包,从而实现能够即时生成定制逻辑的高阶函数——非常适合创建可重用的工具。

ClosureAsReturnValue.groovy
def discountFactory(int percent) {
    { price -> price * (1 - percent / 100) }
}

def tenOff = discountFactory(10)
println tenOff(50)

discountFactory 返回一个应用百分比折扣的闭包。tenOff 提供 10% 的折扣,这对于零售系统很有用,在该系统中,不同的销售活动需要自定义的折扣计算器,而无需重复代码。

闭包和集合

闭包与集合配合得非常好,可以使用简洁、富有表现力的语法简化过滤、转换或聚合数据等任务。

ClosureWithCollections.groovy
def sales = [25.50, 19.99, 45.00, 10.75, 60.20]

def withTax = sales.collect { it * 1.07 }
println withTax

def highSales = sales.findAll { it > 20 }
println highSales

collect 将 7% 的税应用于每笔销售,而 findAll 过滤出超过 20 美元的销售额。设想一下在销售仪表板中,只需少量工作即可处理每日数据或突出显示大型交易。

闭包和作用域

闭包会捕获其周围的作用域,即使在其创建上下文消失后也能访问外部变量。这种“记忆”功能使其成为状态化逻辑的强大工具。

ClosureScope.groovy
def counter = 0
def increment = { counter++ }

increment(); increment()
println "Count: $counter"

increment 修改其外部作用域中的 counter,就像 Web 应用程序中的点击跟踪器一样。每次调用都会更新共享状态,显示闭包如何轻松地连接局部数据和持久化数据。

Currying 闭包

Currying 将一个多参数闭包转换为一个具有预设参数的专用版本,通过预配置的逻辑简化重复任务。

CurryingClosure.groovy
def logEvent = { level, msg -> println "[$level] $msg" }
def logInfo = logEvent.curry("INFO")

logInfo("User logged in")

logEvent 使用级别和消息进行日志记录;logInfo 将级别锁定为“INFO”。这适用于日志级别信息日志很常见的日志记录系统,避免了您每次都重复记录级别。

闭包和委托

闭包可以将方法调用委托给另一个对象,从而动态地将行为路由到指定的“助手”,以实现自适应、上下文驱动的执行。

ClosureDelegation.groovy
class Notifier {
    String notify(String user) { "Alert sent to $user" }
}

def alert = { notify("Alice") }
alert.delegate = new Notifier()
alert.resolveStrategy = Closure.DELEGATE_FIRST

println alert()

alertnotify 委托给一个 Notifier 实例,该实例由 delegateDELEGATE_FIRST 设置。这可能会在监控系统中触发通知,允许闭包动态地访问外部逻辑。

用于验证的闭包

闭包可以强制执行规则,使其非常适合在处理数据之前验证数据,并且可以使用针对特定需求的重用逻辑。

ValidationClosure.groovy
def isValidEmail = { email ->
    email =~ /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
}

def email = "user@domain.com"
println isValidEmail(email) ? "Valid email" : "Invalid email"

isValidEmail 检查电子邮件是否与正则表达式匹配,并返回一个布尔值。这在注册表单中非常有用,该闭包在提交前确保电子邮件有效性,从而使验证保持简洁且可移植。

用于事件处理的闭包

闭包非常适合作为事件处理程序,以自定义操作响应触发器,非常适合 UI 或系统交互。

EventClosure.groovy
def onClick = { event ->
    println "Button clicked at ${new Date()} by $event.user"
}

def simulateClick = { Closure handler ->
    handler([user: "Alice"])
}

simulateClick(onClick)

onClick 使用时间戳和用户记录按钮点击事件,该事件会传递给 simulateClick。这模拟了一个 GUI 框架,其中闭包处理用户操作,无缝地结合了时间和上下文。

闭包组合

闭包可以组合,通过链式转换创建强大、模块化的数据管道,例如逐步处理用户输入。

ComposeClosure.groovy
def trim = { it.trim() }
def capitalize = { it.capitalize() }
def formatName = trim >> capitalize

println formatName("  alice  ")

formatName 使用 >> 运算符结合 trimcapitalize,用于清理和格式化名称。这对于表单处理器来说非常理想,它展示了闭包如何整洁地堆叠操作。

使用闭包的最佳实践

来源

Groovy 闭包文档

本教程深入探讨了 Groovy 闭包,揭示了它们作为灵活、函数式代码块的力量。通过实际示例,我们看到了它们如何以优雅和轻松的方式转变编码任务,从数据处理到事件管理。

作者

我叫 Jan Bodnar,我是一位拥有多年编程经验的热情程序员。自 2007 年以来,我一直在撰写编程文章。到目前为止,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有八年以上的经验。

列出 所有 Groovy 教程