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 闭包的执行方式

闭包有两种执行方式:

  1. 像普通方法一样直接调用
  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 仓库 中找到。


原始标题:Closures in Groovy