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" // 分号省略,括号也省略了
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
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() |
.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的 methodMissing 和 propertyMissing 可以让你拦截未定义的方法和属性调用。这是构建灵活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]
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的四个核心模块串起来了。语法特性是基础,闭包是灵魂,集合操作是日常工具,DSL构建是最终目标。你不需要一次性全部记住,但至少要理解它们之间的关系。
好了,Groovy的基础内容就到这里。这些知识足够你开始阅读和编写Gradle构建脚本了。下一章我们会深入Gradle的构建生命周期,到时候你会看到这些Groovy特性是如何在实际项目中发挥作用的。
公众号:蓝海资料掘金营,微信deep3321