2、Kotlin基础速览:变量与函数、Lambda表达式、扩展函数、委托属性
好,咱们直接进入正题。这一章我打算带你快速过一遍 Kotlin 里最核心的几个语法点。为什么要先讲这个?因为 Gradle Kotlin DSL 本质上就是用 Kotlin 写脚本。你如果不熟悉 Kotlin,看构建脚本就像看天书。我当年刚转 Kotlin 时,就被它那些「奇奇怪怪」的语法搞得一头雾水——后来才发现,其实就那么几个套路。
咱们今天要聊的四个东西:变量与函数、Lambda 表达式、扩展函数、委托属性。这四样东西,你在 Gradle 脚本里几乎天天见。掌握了它们,你就能看懂 90% 的构建脚本。
核心观点:Kotlin 的语法设计目标就是「少写样板代码,多表达意图」。你写的每一行代码,都应该是在告诉别人「我要干什么」,而不是「我怎么干」。
2.1 变量与函数:一切从这里开始
先说说变量。Kotlin 里声明变量有两种方式:val 和 var。说白了,val 是只读的,赋值后就不能改了;var 是可变的,随便改。我个人习惯是:能用 val 绝不用 var。为什么?因为不可变的东西不容易出 bug。我在项目中遇到过好几次,就是因为某个变量被意外修改了,排查了半天才发现是 var 惹的祸。
// val 是只读的,相当于 Java 里的 final
val name: String = "Gradle"
// name = "Maven" // 编译报错!不能重新赋值
// var 是可变的
var version: Int = 8
version = 9 // 没问题
Kotlin 还有一个很爽的特性:类型推断。你不需要每次都写类型,编译器能自己猜出来。比如上面那个 name,你写成 val name = "Gradle" 完全没问题。不过我个人建议:在公开 API 或复杂表达式里,还是显式写出类型比较好——方便别人读代码。
函数声明也很简洁。用 fun 关键字,参数类型写在冒号后面,返回值类型也写在冒号后面:
// 带返回值的函数
fun add(a: Int, b: Int): Int {
return a + b
}
// 单表达式函数,可以省略花括号和 return
fun add(a: Int, b: Int) = a + b
// 默认参数值——这个在 Gradle 脚本里很常见
fun greet(name: String, greeting: String = "Hello") {
println("$greeting, $name!")
}
小技巧:Kotlin 支持命名参数。调用函数时可以指定参数名,这样就不用管参数顺序了。比如 greet(greeting = "Hi", name = "Kotlin")。这在 Gradle 脚本里配置任务时特别有用。
2.2 Lambda 表达式:代码块就是数据
Lambda 表达式,说白了就是一个可以传递的代码块。你想想看,在 Java 里你要传一个行为,得写匿名内部类,啰嗦得很。Kotlin 里直接用 { } 搞定。
// 最简单的 Lambda
val sum = { x: Int, y: Int -> x + y }
println(sum(3, 4)) // 输出 7
// 如果 Lambda 只有一个参数,可以用 it 代替
val numbers = listOf(1, 2, 3, 4)
val doubled = numbers.map { it * 2 }
println(doubled) // [2, 4, 6, 8]
这里有个关键点:如果 Lambda 是函数的最后一个参数,可以把它写在圆括号外面。这就是 Gradle 脚本里那种「花括号嵌套」写法的来源。比如:
// 常规写法
numbers.map({ it * 2 })
// 简写——最后一个参数是 Lambda,可以提出来
numbers.map() { it * 2 }
// 如果只有 Lambda 一个参数,圆括号都可以省略
numbers.map { it * 2 }
我记得刚开始用 Gradle Kotlin DSL 时,看到这种写法一脸懵。后来才明白,其实就是 Lambda 的语法糖。你写 tasks.register("build") { ... },那个花括号里的内容就是一个 Lambda 表达式,它被传递给 register 函数作为配置块。
重点:Lambda 表达式在 Gradle 脚本里无处不在。每一个 configure<T> { }、每一个 tasks.named<T>("...") { },本质上都是在传递 Lambda。理解了这个,你就看懂了 Gradle 脚本的骨架。
2.3 扩展函数:给别人的类加方法
这个特性我特别喜欢。扩展函数允许你给一个已有的类添加新方法,而不需要继承它。说白了,就是「偷梁换柱」——你可以在不修改源码的情况下,给任何类增加功能。
// 给 String 类添加一个扩展函数
fun String.addExclamation(): String {
return this + "!"
}
println("Hello".addExclamation()) // 输出 "Hello!"
// 给 List 添加扩展函数
fun <T> List<T>.secondOrNull(): T? {
return if (size >= 2) this[1] else null
}
val list = listOf(1, 2, 3)
println(list.secondOrNull()) // 输出 2
在 Gradle 脚本里,扩展函数用得那叫一个多。比如 DependencyHandler 这个类,它本身没有 implementation 方法,但 Gradle 通过扩展函数给它加上了。你写的 implementation("com.example:lib:1.0"),其实就是调用了扩展函数。
注意:扩展函数并不是真的修改了原始类。它只是一个静态方法,第一个参数是接收者对象。所以扩展函数不能访问私有成员。我曾经踩过这个坑——想给一个第三方库的类加扩展函数,结果发现它需要的字段是私有的,根本访问不到。
2.4 委托属性:懒加载与观察者模式
委托属性是 Kotlin 里比较高级的特性,但在 Gradle 脚本里很常见。它的核心思想是:属性的 getter/setter 逻辑可以委托给另一个对象。
最常用的就是 lazy 委托——属性只在第一次访问时初始化,之后缓存结果:
// lazy 委托——懒加载
val config: String by lazy {
println("初始化 config...")
"这是配置内容"
}
println(config) // 第一次访问,输出 "初始化 config..." 然后输出 "这是配置内容"
println(config) // 第二次访问,直接输出 "这是配置内容",不再初始化
为什么这个在 Gradle 里重要?因为 Gradle 的配置阶段和执行阶段是分开的。很多属性你希望只在需要时才初始化,而不是在脚本加载时就计算。用 lazy 就能做到这一点。
还有一个是 observable 委托——属性变化时自动触发回调:
import kotlin.properties.Delegates
var watched: String by Delegates.observable("初始值") {
prop, old, new ->
println("属性 ${prop.name} 从 $old 变成了 $new")
}
watched = "新值" // 输出:属性 watched 从 初始值 变成了 新值
我个人在实际项目中用得最多的是 lazy。比如在 Gradle 插件里,有些配置对象需要从文件中读取,但又不是每次都用得到。用 lazy 委托,就能避免不必要的 I/O 操作。
避坑指南:我曾经在 Gradle 脚本里用 lazy 委托了一个需要读取环境变量的属性。结果发现,在配置阶段环境变量还没设置好,导致第一次访问时拿到了空值。后来我改用 Provider 接口才解决。所以记住:lazy 只适合那些「初始化时就能确定值」的场景。
小结一下
好了,这四个知识点咱们过了一遍。你可能会觉得内容有点多,但其实核心就一句话:Kotlin 的语法设计是为了让你少写废话。变量用 val 还是 var,函数怎么写,Lambda 怎么传,扩展函数怎么加,委托属性怎么用——这些都是在帮你把「意图」表达得更清晰。
下一节我们会把这些知识串起来,看看它们在 Gradle 脚本里具体是怎么用的。嗯,到时候你会发现,原来那些看起来「花里胡哨」的脚本,底层就是这么几个简单的套路。