第3章 Groovy基础速通:变量、字符串、集合、闭包
讲Gradle之前,我得先带你过一遍Groovy。为什么要先学Groovy?说白了,Gradle的构建脚本就是Groovy代码。你想想看,一个不懂Groovy的人去写Gradle脚本,就像拿着英文词典硬背法语单词——能凑合,但永远写不出优雅的脚本。
我最早接触Gradle时,Groovy基础几乎为零。当时遇到一个复杂的多模块构建,闭包怎么也写不对,编译错误满天飞。后来花了三天把Groovy核心啃完,回头再看Gradle,豁然开朗。所以这一章,我们只讲写Gradle脚本最常用的那部分Groovy。绝不讲那些用不上的冷门语法。
3.1 变量——用def就够了
Groovy里的变量声明,我个人习惯几乎只用def。为什么?因为Groovy是动态类型语言,def让编译器自己去推断类型。你写def x = 10,它知道是int;写def name = "Tom",它知道是String。
关键点:在Gradle脚本里,绝大多数变量都用def声明。只有极少数需要强类型的地方(比如声明一个Task类型变量),才显式写出类型。
// Groovy变量示例
def projectName = "MyApp" // String
def versionCode = 1 // int
def isDebug = true // boolean
def dependencies = [] // ArrayList
// 甚至可以随意改变类型(但不推荐)
def tricky = "hello"
tricky = 42 // 合法,但容易出bug
我在项目中遇到过一个问题:有个同事在Gradle脚本里用int versionCode = 1,后来需求变化需要改成浮点数,结果到处都要改类型。如果用def,一行搞定。所以啊,除非你明确需要类型约束,否则统统用def。
注意:Groovy的变量作用域与Java不同。在脚本顶层用def声明的变量是局部变量,不会自动变成属性。如果你想让它在闭包内也能访问,最好使用ext命名空间——这个我们后面章节会细讲。
3.2 字符串——插值是真的香
写Gradle脚本时,字符串操作太频繁了。版本号拼接、路径拼接、任务描述……Groovy的字符串有三类,但最常用的是双引号字符串——因为它支持插值。
| 字符串类型 | 写法 | 特点 |
|---|---|---|
| 单引号字符串 | 'Hello' | 纯文本,不支持插值 |
| 双引号字符串 | "Hello $name" | 支持插值,支持${表达式} |
| 三引号字符串 | '''多行'''或"""多行""" | 保留换行符,常用于多行SQL/脚本 |
举个例子,你想想看,以前Java里拼接字符串多痛苦:"version: " + major + "." + minor。Groovy里这样写:
def major = 2
def minor = 5
def version = "v$major.$minor" // result: v2.5
def full = "版本号: ${major}.${minor}-beta" // 表达式用${}
我曾经为了调试一个构建任务,需要打印当前Gradle版本和项目名。用插值一行搞定:println "Running Gradle ${gradle.gradleVersion} for project ${project.name}"。舒服。
技巧:三引号字符串("""...""")在Gradle中常用于写长文本,比如自定义task的描述,或者生成README内容。注意三引号默认会保留缩进,如果你不想保留,可以加stripIndent()方法。
3.3 集合——List和Map就是Gradle的骨架
Gradle里到处都是集合。依赖列表、插件集合、任务配置……Groovy让集合操作变得极其简单。来看最常用的两种:
List(列表)
def fruits = ['apple', 'banana', 'orange']
fruits << 'grape' // 添加元素:groovy操作符重载
fruits += 'mango' // 也可以用+=
println fruits[0] // apple
fruits.each { println it } // 遍历,it是隐式参数
我在项目中遇到过场景:需要动态添加依赖。用dependencies { implementation 'xxx' }其实是闭包里的快捷操作,背后也是List。你甚至可以用dependencies.add('xxx'),但没人这么写——太啰嗦了。
Map(映射)
def config = [name: 'MyApp', version: 1.0, debug: true]
println config.name // MyApp (map.key语法)
println config['version'] // 1.0
config.minSdk = 21 // 动态添加
config.each { k, v -> println "$k = $v" }
Map在Gradle里无处不在,比如android { defaultConfig { ... } }本质上就是嵌套的Map操作。我个人习惯使用map.key而不是map['key'],因为更简洁。但注意,如果key是保留字或包含特殊字符,必须用方括号。
重点:Groovy的each、collect、find等遍历方法,在Gradle脚本里高频出现。建议你把它们练熟。
3.4 闭包——Gradle的灵魂
闭包(Closure)是Groovy最强大的特性,也是Gradle构建脚本的核心。说实在的,如果你不会闭包,等于不会Gradle。闭包本质上是一段可执行的代码块,可以赋值给变量、作为参数传递。
闭包的基本语法
// 最简单的闭包
def myClosure = { println "Hello" }
myClosure() // 执行
// 带参数的闭包
def greet = { String name -> println "Hi, $name" }
greet("Tom") // Hi, Tom
// 隐式参数 it(当只有一个参数时)
def square = { it * it }
println square(5) // 25
你可能会问:闭包和Java的Lambda有什么区别?答案:闭包更灵活。它可以访问外部变量(闭包捕获),可以修改委托对象(delegate),这些在Gradle里用得飞起。
闭包在Gradle中的经典用法
// 传递一个闭包给task创建
task myTask {
doLast {
println "任务执行完毕"
}
}
// 上面的花括号 { doLast { ... } } 就是闭包
// myTask 其实接收一个闭包作为配置
// 闭包委托(delegate)——Gradle DSL的精髓
// 在android闭包里,你可以直接写 compileSdk 34
android {
compileSdk 34
defaultConfig {
applicationId "com.example"
}
}
// 每个闭包都有一个delegate,指向对应的Extension对象
我曾踩过一个坑:在闭包里访问外部变量,结果发现变量值是null。原因是我在闭包内使用了错误的委托。记住:Gradle的配置闭包默认委托给Project或Extension对象,所以要区分delegate和owner。不过新手只需知道:在android {}里直接写属性,系统会自动找到对应的setter方法。
避坑指南:我曾经在闭包内使用return,以为它会跳出闭包,实际上它会导致整个闭包返回。闭包的最后一句表达式就是返回值,不需要显式return。举个例子:def result = { a, b -> a + b },调用result(1,2)返回3。如果你写return a+b,结果一样,但很多Groovy开发者觉得不美观。
3.5 把这些串起来——一个实际Gradle片段
我们来看一个综合的例子,把变量、字符串、集合、闭包全用上:
def appName = "MyApp"
def versionMajor = 1
def versionMinor = 0
def supportedLanguages = ['zh', 'en', 'ja']
task printConfig {
doLast {
println "应用名称: $appName"
println "版本: ${versionMajor}.${versionMinor}"
println "支持语言: $supportedLanguages"
}
}
// 用闭包定义一个配置块
ext {
versionCode = versionMajor * 1000 + versionMinor
versionName = "${versionMajor}.${versionMinor}.0"
}
println "versionCode = $versionCode"
这段脚本里,变量用了def,字符串用了插值,集合用了List,闭包分别用于doLast和ext {}。你看,这才几行代码,Groovy的核心语法全出现了。所以别怕,就这几个概念,练熟就行。
我的学习建议:打开你项目里的build.gradle文件,试着找出所有变量、字符串、集合和闭包。把它们标出来,看它们在Gradle上下文里是怎么工作的。自己改几行,跑一下,很快就能上手。
3.6 小结:你只需要记住这些
- 变量:一律用
def,除非有特殊需求。 - 字符串:双引号+插值最方便。多行长文本用三引号。
- 集合:List用
[],Map用[:],记得each遍历。 - 闭包:
{ 参数 -> 代码 },默认it参数。在Gradle里用于配置和任务定义。