Groovy基础速通:语法特性、闭包、集合操作与DSL构建

说实话,很多Java开发者第一次看到Groovy代码时,会觉得有点「怪」。但一旦你理解了它的设计哲学——少写代码,多干活——你就会爱上它。我在做Gradle插件开发时,Groovy就是我的主力语言。今天我们就来快速过一遍Groovy的核心知识点,这些内容足够你应付Gradle构建脚本的编写了。

一、Groovy语法特性:Java的「轻量版」

Groovy是运行在JVM上的动态语言。它和Java高度兼容,但做了大量简化。我个人习惯把它看作「Java的语法糖加强版」。

1. 可选类型与def关键字

在Java里,你写 String name = "hello"。在Groovy里,你可以直接写 def name = "hello"。def表示「我不关心具体类型,让编译器自己去推断」。

// Java风格
String message = "Hello Gradle"

// Groovy风格
def message = "Hello Gradle"

我在项目中遇到过一个问题:过度使用def会让代码可读性下降。尤其是方法返回值,我建议明确声明类型,而不是全部用def。

2. 分号可选与括号省略

Groovy里,行尾分号可以省略。方法调用时,如果参数是字符串或闭包,括号也可以省略。这看起来有点「任性」,但写起来确实爽。

// Java
System.out.println("Hello");

// Groovy
println "Hello"  // 分号省略,括号也省略了
小技巧:在Gradle构建脚本中,你经常看到 apply plugin: 'java' 这种写法。这其实就是省略了括号的方法调用——apply(plugin: 'java')

3. 字符串的三种形式

Groovy支持单引号、双引号和三引号字符串。其中双引号字符串支持插值(GString),这是我最常用的特性。

def name = "Gradle"
println "Hello, ${name}"  // 输出:Hello, Gradle
println 'Hello, ${name}'  // 输出:Hello, ${name}(单引号不插值)

三引号字符串用于多行文本,写DSL时特别方便。

def script = """
task hello {
    doLast {
        println 'Hello'
    }
}
"""

4. 安全导航操作符 ?.

这个特性我太喜欢了。在Java里,你要写一堆if判空。在Groovy里,一个 ?. 搞定。

def person = null
println person?.name  // 不会抛NPE,输出null

我曾经在项目里用这个特性重构了一段200行的判空逻辑,最后只用了20行。嗯,代码审查时同事都看呆了。

二、闭包:Groovy的灵魂

闭包是Groovy最核心的概念。说白了,闭包就是一段可执行的代码块,你可以把它当作参数传递、赋值给变量、甚至作为返回值。

1. 闭包的定义与调用

// 定义一个闭包
def hello = { String name ->
    println "Hello, ${name}"
}

// 调用闭包
hello("Gradle")  // 输出:Hello, Gradle
hello.call("Gradle")  // 另一种调用方式

闭包的参数列表在 -> 左边,执行体在右边。如果只有一个参数,可以省略参数声明,用隐式的 it 变量。

def square = { it * it }
println square(5)  // 输出:25

2. 闭包与集合的「化学反应」

闭包最常见的用途就是操作集合。Gradle的构建逻辑里,你到处都能看到这种模式。

def numbers = [1, 2, 3, 4, 5]

// each:遍历
numbers.each { println it }

// collect:映射
def squares = numbers.collect { it * it }
println squares  // [1, 4, 9, 16, 25]

// find/findAll:查找
def even = numbers.findAll { it % 2 == 0 }
println even  // [2, 4]

// inject:归约
def sum = numbers.inject(0) { acc, val -> acc + val }
println sum  // 15
重点:Gradle的Task配置块本质上就是闭包。当你写 task hello { ... } 时,花括号里的内容就是一个闭包,它会在Task创建时被延迟执行。

3. 闭包的委托机制

这是Groovy DSL的基石。闭包有一个 delegate 属性,你可以改变它,让闭包内部的代码在另一个对象上下文中执行。

class Person {
    String name
    def sayHello() { println "Hello, I'm ${name}" }
}

def cl = { name = "Gradle"; sayHello() }
cl.delegate = new Person()
cl()  // 输出:Hello, I'm Gradle

Gradle的构建脚本就是利用这个机制,让你在闭包中直接调用Task的属性和方法,而不用写 task.setProperty(...) 这种啰嗦的代码。

三、集合操作:简洁到令人发指

Groovy对集合的操作做了大量简化。你想想看,在Java里要写多少行才能完成一个简单的分组操作?在Groovy里,一行搞定。

1. 列表与映射的创建

// 列表
def list = [1, 2, 3, 4, 5]
def emptyList = []

// 映射
def map = [name: 'Gradle', version: '8.0']
def emptyMap = [:]

// 访问元素
println list[0]      // 1
println map.name     // Gradle
println map['name']  // Gradle

2. 强大的操作符重载

def list = [1, 2, 3]

// 添加元素
list << 4  // 使用左移操作符
println list  // [1, 2, 3, 4]

// 范围操作符
def range = 1..5
println range  // [1, 2, 3, 4, 5]

// 展开操作符 *
def a = [1, 2]
def b = [3, 4]
def c = [*a, *b]
println c  // [1, 2, 3, 4]

3. 常用集合方法速查

方法 作用 示例
each 遍历 list.each { println it }
collect 映射转换 list.collect { it * 2 }
findAll 过滤 list.findAll { it > 2 }
groupBy 分组 list.groupBy { it % 2 }
sort 排序 list.sort()
unique 去重 list.unique()
注意:Groovy的集合方法很多会修改原集合。如果你不想修改原数据,记得调用 .collect().findAll() 等返回新集合的方法,而不是 .each()

四、DSL构建基础:让代码像说话一样自然

DSL(领域特定语言)是Groovy的杀手锏。Gradle的构建脚本本质上就是一个DSL。你写的每一行配置,都是在「描述」构建过程,而不是「命令」计算机去执行。

1. DSL的核心三要素

要构建一个DSL,你需要掌握三个东西:闭包委托、方法拦截、属性访问。

// 一个简单的DSL示例
class Project {
    String name
    String version
    List<Task> tasks = []

    def task(String name, Closure config) {
        def t = new Task(name: name)
        config.delegate = t
        config()
        tasks << t
    }
}

class Task {
    String name
    String description
    def doLast(Closure action) {
        println "Executing ${name}: ${description}"
        action()
    }
}

// 使用DSL
def project = new Project()
project.name = "MyApp"
project.version = "1.0"

project.task("build") {
    description = "Build the project"
    doLast {
        println "Building..."
    }
}

你看,这段代码读起来就像自然语言一样:「创建一个项目,名字叫MyApp,版本1.0。添加一个build任务,描述是构建项目,执行时打印Building...」。

2. 方法拦截与动态属性

Groovy的 methodMissingpropertyMissing 可以让你拦截未定义的方法和属性调用。这是构建灵活DSL的关键。

class DynamicDSL {
    def properties = [:]

    def propertyMissing(String name) {
        properties[name]
    }

    def propertyMissing(String name, value) {
        properties[name] = value
    }

    def methodMissing(String name, args) {
        println "调用未定义方法: ${name}, 参数: ${args}"
    }
}

def dsl = new DynamicDSL()
dsl.name = "Gradle"  // 动态属性
println dsl.name     // 输出:Gradle
dsl.hello("world")   // 输出:调用未定义方法: hello, 参数: [world]
实战经验:我在开发Gradle插件时,经常用 methodMissing 来实现「约定优于配置」。比如用户写了一个不存在的配置项,我可以优雅地忽略它,而不是抛异常。

3. DSL的构建模式

常见的DSL构建模式有两种:嵌套闭包和链式调用。Gradle主要使用嵌套闭包模式。

// 嵌套闭包模式(Gradle风格)
configure {
    repositories {
        mavenCentral()
    }
    dependencies {
        implementation 'org.apache.commons:commons-lang3:3.12.0'
    }
}

// 链式调用模式(类似Builder模式)
def result = new Builder()
    .setName("test")
    .setVersion("1.0")
    .addTask("build")
    .build()

我个人更推荐嵌套闭包模式,因为它更符合「声明式」的思维。你想想看,写Gradle脚本时,你是在「描述」项目结构,而不是在「调用」一系列方法。

五、知识体系总览

下面这张图总结了本章的核心知识点,你可以把它当作快速回顾的索引。

Groovy基础速通 语法特性 def关键字 · 可选分号 字符串插值 · GString 安全导航 ?. · Elvis ?: 闭包 定义与调用 · 隐式it 委托机制 · delegate 作为参数传递 集合操作 列表/映射 · 范围 each/collect/findAll groupBy/inject/sort DSL构建基础 闭包委托 + 方法拦截 嵌套闭包模式 动态属性/方法

这张图把Groovy的四个核心模块串起来了。语法特性是基础,闭包是灵魂,集合操作是日常工具,DSL构建是最终目标。你不需要一次性全部记住,但至少要理解它们之间的关系。

好了,Groovy的基础内容就到这里。这些知识足够你开始阅读和编写Gradle构建脚本了。下一章我们会深入Gradle的构建生命周期,到时候你会看到这些Groovy特性是如何在实际项目中发挥作用的。


公众号:蓝海资料掘金营,微信deep3321