1. 简介
本 Codelab 将教您函数类型、如何使用函数类型以及 Lambda 表达式特有的语法。
在 Kotlin 中,函数被视为一等构造(first-class constructs)。这意味着函数可以像数据类型一样对待。您可以将函数存储在变量中,将它们作为参数传递给其他函数,以及从其他函数返回函数。
与其他可以使用字面值表示的数据类型一样——例如值 10 的 Int 类型,以及值 "Hello" 的 String 类型——您也可以声明函数字面值,它们称为 Lambda 表达式或简称为 Lambda。在 Android 开发中以及更广泛的 Kotlin 编程中,您会大量使用 Lambda 表达式。
前提条件
- 熟悉 Kotlin 编程,包括函数、
if/else语句和可空性
您将学到什么
- 如何使用 Lambda 语法定义函数。
- 如何将函数存储在变量中。
- 如何将函数作为参数传递给其他函数。
- 如何从其他函数返回函数。
- 如何使用可空函数类型。
- 如何使 Lambda 表达式更简洁。
- 什么是高阶函数。
- 如何使用
repeat()函数。
您需要准备什么
- 可访问 Kotlin Playground 的网页浏览器
2. 观看同步编码视频(可选)
如果您想观看其中一位课程讲师完成此 Codelab,请播放下面的视频。
建议将视频展开至全屏(使用视频角落的此图标
),以便您更清楚地看到 Kotlin Playground 和代码。
此步骤是可选的。您也可以跳过视频,直接开始 Codelab 说明。
3. 将函数存储在变量中
到目前为止,您学习了如何使用 fun 关键字声明函数。使用 fun 关键字声明的函数可以调用,从而导致函数体中的代码执行。
作为一等构造,函数也是数据类型,因此您可以将函数存储在变量中,将它们传递给函数,并从函数返回它们。也许您希望能够在运行时更改应用某个部分的 behavior,或者像之前 Codelab 中那样嵌套 composable 函数来构建布局。所有这一切都可通过 Lambda 表达式实现。
您可以通过一些不给糖就捣蛋(Trick-or-treating)来了解其实际应用,这指的是许多国家/地区的万圣节传统,孩子们穿着盛装挨家挨户地问“不给糖就捣蛋”,通常会收到糖果作为回报。
将函数存储在变量中
- 前往Kotlin Playground。
- 在
main()函数之后,定义一个不带参数且没有返回值的trick()函数,该函数打印"No treats!"。其语法与您在之前 Codelabs 中看到的其他函数相同。
fun main() {
}
fun trick() {
println("No treats!")
}
- 在
main()函数体中,创建一个名为trickFunction的变量,并将其设置为等于trick。您无需在trick后添加括号,因为您希望将函数存储在变量中,而不是调用该函数。
fun main() {
val trickFunction = trick
}
fun trick() {
println("No treats!")
}
- 运行代码。会产生错误,因为 Kotlin 编译器将
trick识别为trick()函数的名称,但它期望您调用该函数,而不是将其赋值给变量。
Function invocation 'trick()' expected
您尝试将 trick 存储在 trickFunction 变量中。但是,要将函数作为值引用,您需要使用函数引用运算符(::)。语法如这张图所示:

- 要将函数作为值引用,请将
trickFunction重新赋值为::trick。
fun main() {
val trickFunction = ::trick
}
fun trick() {
println("No treats!")
}
- 运行代码以验证是否没有更多错误。您会看到一条警告,提示
trickFunction从未使用过,但这将在下一部分中修复。
使用 Lambda 表达式重新定义函数
Lambda 表达式提供了一种简洁的语法,无需 fun 关键字即可定义函数。您可以将 Lambda 表达式直接存储在变量中,而无需对另一个函数进行函数引用。
在赋值运算符(=)之前,您添加 val 或 var 关键字,后跟变量名,这是您调用函数时使用的名称。在赋值运算符(=)之后是 Lambda 表达式,它由一对花括号组成,形成函数体。语法如这张图所示:

使用 Lambda 表达式定义函数时,您会得到一个引用该函数的变量。您也可以像任何其他类型一样将其值赋给其他变量,并使用新变量的名称调用该函数。
更新代码以使用 Lambda 表达式
- 使用 Lambda 表达式重写
trick()函数。trick名称现在指向变量名。花括号中的函数体现在是一个 Lambda 表达式。
fun main() {
val trickFunction = ::trick
}
val trick = {
println("No treats!")
}
- 在
main()函数中,移除函数引用运算符(::),因为trick现在引用的是一个变量,而不是函数名。
fun main() {
val trickFunction = trick
}
val trick = {
println("No treats!")
}
- 运行代码。没有错误,您可以直接引用
trick()函数,而无需函数引用运算符(::)。没有输出,因为您还没有调用该函数。 - 在
main()函数中,调用trick()函数,但这次要像调用任何其他函数一样包含括号。
fun main() {
val trickFunction = trick
trick()
}
val trick = {
println("No treats!")
}
- 运行代码。Lambda 表达式的主体被执行。
No treats!
- 在
main()函数中,像调用函数一样调用trickFunction变量。
fun main() {
val trickFunction = trick
trick()
trickFunction()
}
val trick = {
println("No treats!")
}
- 运行代码。函数被调用了两次,一次是
trick()函数调用,第二次是trickFunction()函数调用。
No treats! No treats!
使用 Lambda 表达式,您可以创建存储函数的变量,像调用函数一样调用这些变量,并将它们存储在您可以像调用函数一样调用的其他变量中。
4. 将函数用作数据类型
您在之前的 Codelab 中了解到,Kotlin 具有类型推断。声明变量时,通常不需要显式指定类型。在前面的示例中,Kotlin 编译器能够推断出 trick 的值是一个函数。但是,如果您想指定函数参数或返回值的类型,则需要了解表示函数类型的语法。函数类型由一对圆括号组成,其中包含可选的参数列表、-> 符号和返回类型。语法如这张图所示:

您之前声明的 trick 变量的数据类型是 () -> Unit。圆括号为空,因为函数没有参数。返回类型是 Unit,因为函数不返回任何内容。如果您有一个接受两个 Int 参数并返回一个 Int 的函数,则其数据类型将是 (Int, Int) -> Int。
使用 Lambda 表达式声明另一个显式指定函数类型的函数
- 在
trick变量之后,声明一个名为treat的变量,使其等于一个 Lambda 表达式,其函数体打印"Have a treat!"。
val trick = {
println("No treats!")
}
val treat = {
println("Have a treat!")
}
- 将
treat变量的数据类型指定为() -> Unit。
val treat: () -> Unit = {
println("Have a treat!")
}
- 在
main()函数中,调用treat()函数。
fun main() {
val trickFunction = trick
trick()
trickFunction()
treat()
}
- 运行代码。
treat()函数的行为与trick()函数类似。尽管只有treat变量显式声明了数据类型,但这两个变量都具有相同的数据类型。
No treats! No treats! Have a treat!
将函数用作返回类型
函数是一种数据类型,因此您可以像使用其他任何数据类型一样使用它。您甚至可以从其他函数返回函数。语法如这张图所示:

创建一个返回函数的函数。
- 删除
main()函数中的代码。
fun main() {
}
- 在
main()函数之后,定义一个trickOrTreat()函数,该函数接受类型为Boolean的isTrick参数。
fun main() {
}
fun trickOrTreat(isTrick: Boolean): () -> Unit {
}
val trick = {
println("No treats!")
}
val treat = {
println("Have a treat!")
}
- 在
trickOrTreat()函数体中,添加一个if语句,如果isTrick为true,则返回trick()函数;如果isTrick为 false,则返回treat()函数。
fun trickOrTreat(isTrick: Boolean): () -> Unit {
if (isTrick) {
return trick
} else {
return treat
}
}
- 在
main()函数中,创建一个名为treatFunction的变量,并将其赋值为调用trickOrTreat()的结果,其中isTrick参数传入false。然后,创建第二个变量,名为trickFunction,并将其赋值为调用trickOrTreat()的结果,这次isTrick参数传入true。
fun main() {
val treatFunction = trickOrTreat(false)
val trickFunction = trickOrTreat(true)
}
- 调用
treatFunction(),然后在下一行调用trickFunction()。
fun main() {
val treatFunction = trickOrTreat(false)
val trickFunction = trickOrTreat(true)
treatFunction()
trickFunction()
}
- 运行代码。您应该会看到每个函数的输出。即使您没有直接调用
trick()或treat()函数,您仍然可以调用它们,因为您存储了每次调用trickOrTreat()函数的返回值,并使用trickFunction和treatFunction变量调用了这些函数。
Have a treat! No treats!
现在您了解了函数如何返回其他函数。您还可以将函数作为参数传递给另一个函数。也许您想为 trickOrTreat() 函数提供一些自定义行为,使其执行除返回两个字符串之一以外的其他操作。接受另一个函数作为参数的函数允许您每次调用时传递不同的函数。
将函数作为参数传递给另一个函数
在世界上一些庆祝万圣节的地方,孩子们会收到零钱而不是糖果,或者两者都收到。您将修改 trickOrTreat() 函数,允许将由函数表示的额外款待作为参数提供。
作为 trickOrTreat() 参数使用的函数也需要有自己的参数。声明函数类型时,参数没有标签。您只需要指定每个参数的数据类型,并用逗号分隔。语法如这张图所示:

当您为接受参数的函数编写 Lambda 表达式时,参数会按照出现的顺序获得名称。参数名称列在开花括号之后,每个名称用逗号分隔。箭头(->)将参数名称与函数体分隔开。语法如这张图所示:

更新 trickOrTreat() 函数以接受函数作为参数
- 在
isTrick参数之后,添加一个类型为(Int) -> String的extraTreat参数。
fun trickOrTreat(isTrick: Boolean, extraTreat: (Int) -> String): () -> Unit {
- 在
else块中,在return语句之前,调用println(),并在其中传入对extraTreat()函数的调用。将5传入对extraTreat()的调用。
fun trickOrTreat(isTrick: Boolean, extraTreat: (Int) -> String): () -> Unit {
if (isTrick) {
return trick
} else {
println(extraTreat(5))
return treat
}
}
- 现在,当您调用
trickOrTreat()函数时,需要使用 Lambda 表达式定义一个函数,并将其作为extraTreat参数传入。在main()函数中,在调用trickOrTreat()函数之前,添加一个coins()函数。coins()函数将Int参数命名为quantity并返回一个String。您可能会注意到没有return关键字,它不能用于 Lambda 表达式。相反,函数中最后一个表达式的结果成为返回值。
fun main() {
val coins: (Int) -> String = { quantity ->
"$quantity quarters"
}
val treatFunction = trickOrTreat(false)
val trickFunction = trickOrTreat(true)
treatFunction()
trickFunction()
}
- 在
coins()函数之后,按所示添加一个cupcake()函数。将Int参数命名为quantity,并使用->运算符将其与函数体分隔开。现在您可以将coins()或cupcake()函数传入trickOrTreat()函数。
fun main() {
val coins: (Int) -> String = { quantity ->
"$quantity quarters"
}
val cupcake: (Int) -> String = { quantity ->
"Have a cupcake!"
}
val treatFunction = trickOrTreat(false)
val trickFunction = trickOrTreat(true)
treatFunction()
trickFunction()
}
- 在
cupcake()函数中,移除quantity参数和->符号。它们没有被使用,所以可以省略。
val cupcake: (Int) -> String = {
"Have a cupcake!"
}
- 更新对
trickOrTreat()函数的调用。对于第一次调用,当isTrick为false时,传入coins()函数。对于第二次调用,当isTrick为true时,传入cupcake()函数。
fun main() {
val coins: (Int) -> String = { quantity ->
"$quantity quarters"
}
val cupcake: (Int) -> String = {
"Have a cupcake!"
}
val treatFunction = trickOrTreat(false, coins)
val trickFunction = trickOrTreat(true, cupcake)
treatFunction()
trickFunction()
}
- 运行代码。
extraTreat()函数仅在isTrick参数设置为false参数时被调用,因此输出包括 5 个 quarters,但没有 cupcakes。
5 quarters Have a treat! No treats!
可空函数类型
与其他数据类型一样,函数类型可以声明为可空。在这种情况下,变量可以包含一个函数,也可以是 null。
要将函数声明为可空,请将函数类型用圆括号括起来,并在结束圆括号后添加 ? 符号。例如,如果您想使 () -> String 类型可空,请将其声明为 (() -> String)? 类型。语法如这张图所示:

将 extraTreat 参数设为可空,这样您就不必每次调用 trickOrTreat() 函数时都提供一个 extraTreat() 函数。
- 将
extraTreat参数的类型更改为(() -> String)?。
fun trickOrTreat(isTrick: Boolean, extraTreat: ((Int) -> String)?): () -> Unit {
- 修改对
extraTreat()函数的调用,使用if语句仅在函数非空时调用。现在trickOrTreat()函数应如下所示:
fun trickOrTreat(isTrick: Boolean, extraTreat: ((Int) -> String)?): () -> Unit {
if (isTrick) {
return trick
} else {
if (extraTreat != null) {
println(extraTreat(5))
}
return treat
}
}
- 移除
cupcake()函数,然后在第二次调用trickOrTreat()函数时,将cupcake参数替换为null。
fun main() {
val coins: (Int) -> String = { quantity ->
"$quantity quarters"
}
val treatFunction = trickOrTreat(false, coins)
val trickFunction = trickOrTreat(true, null)
treatFunction()
trickFunction()
}
- 运行代码。输出应该保持不变。现在您可以将函数类型声明为可空,无需再为
extraTreat参数传入函数。
5 quarters Have a treat! No treats!
5. 使用速记语法编写 Lambda 表达式
Lambda 表达式提供了多种使代码更简洁的方法。您将在本节中探索其中几种,因为您遇到和编写的大多数 Lambda 表达式都使用速记语法编写。
省略参数名
编写 coins() 函数时,您为函数的 Int 参数显式声明了名称 quantity。然而,正如您在 cupcake() 函数中看到的那样,您可以完全省略参数名。当函数只有一个参数且您未提供名称时,Kotlin 会隐式将其命名为 it,因此您可以省略参数名和 -> 符号,这使得您的 Lambda 表达式更加简洁。语法如这张图所示:

更新 coins() 函数以使用参数的速记语法
- 在
coins()函数中,移除quantity参数名和->符号。
val coins: (Int) -> String = {
"$quantity quarters"
}
- 将
"$quantity quarters"字符串模板更改为使用$it来引用单个参数。
val coins: (Int) -> String = {
"$it quarters"
}
- 运行代码。Kotlin 识别
Int参数的it参数名,并仍然打印 quarters 的数量。
5 quarters Have a treat! No treats!
将 Lambda 表达式直接传入函数
coins() 函数目前只在一个地方使用。如果您可以直接将 Lambda 表达式传入 trickOrTreat() 函数,而无需先创建一个变量呢?
Lambda 表达式仅仅是函数字面值,就像 0 是整数字面值或 "Hello" 是字符串字面值一样。您可以将 Lambda 表达式直接传入函数调用。语法如这张图所示:

修改代码以移除 coins 变量
- 移动 Lambda 表达式,使其直接传入对
trickOrTreat()函数的调用。您还可以将 Lambda 表达式精简到一行。
fun main() {
val coins: (Int) -> String = {
"$it quarters"
}
val treatFunction = trickOrTreat(false, { "$it quarters" })
val trickFunction = trickOrTreat(true, null)
treatFunction()
trickFunction()
}
- 移除
coins变量,因为它不再使用。
fun main() {
val treatFunction = trickOrTreat(false, { "$it quarters" })
val trickFunction = trickOrTreat(true, null)
treatFunction()
trickFunction()
}
- 运行代码。它仍然可以编译并按预期运行。
5 quarters Have a treat! No treats!
使用尾随 Lambda 语法
当函数类型是函数的最后一个参数时,您可以使用另一种速记选项来编写 Lambda。在这种情况下,您可以将 Lambda 表达式放在调用函数的闭括号之后。语法如这张图所示:

这使得您的代码更具可读性,因为它将 Lambda 表达式与其他参数分开,但不会改变代码的功能。
更新代码以使用尾随 Lambda 语法
- 在
treatFunction变量中,将 Lambda 表达式{"$it quarters"}移动到对trickOrTreat()的调用的闭括号之后。
val treatFunction = trickOrTreat(false) { "$it quarters" }
- 运行代码。一切仍然正常工作!
5 quarters Have a treat! No treats!
6. 使用 repeat() 函数
当一个函数返回一个函数 或 将一个函数作为参数时,它被称为高阶函数。trickOrTreat() 函数就是一个高阶函数的例子,因为它接受一个 ((Int) -> String)? 类型的函数作为参数,并返回一个 () -> Unit 类型的函数。Kotlin 提供了几个有用的高阶函数,您可以利用您对 Lambda 的新知识来使用它们。
repeat() 函数就是这样一个高阶函数。repeat() 函数是一种使用函数表达 for 循环的简洁方式。您将在后面的单元中频繁使用此函数及其他高阶函数。repeat() 函数的函数签名如下:
repeat(times: Int, action: (Int) -> Unit)
times 参数是操作应该发生的次数。action 参数是一个函数,它接受一个 Int 参数并返回一个 Unit 类型。action 函数的 Int 参数是操作到目前为止已执行的次数,例如第一次迭代的参数是 0,第二次迭代的参数是 1。您可以使用 repeat() 函数来重复执行代码指定的次数,类似于 for 循环。语法如这张图所示:

您可以使用 repeat() 函数多次调用 trickFunction() 函数,而不是只调用一次。
更新您的不给糖就捣蛋代码以查看 repeat() 函数的实际应用
- 在
main()函数中,在调用treatFunction()和trickFunction()之间,调用repeat()函数。将4传入times参数,并对action函数使用尾随 Lambda 语法。您无需为 Lambda 表达式的Int参数提供名称。
fun main() {
val treatFunction = trickOrTreat(false) { "$it quarters" }
val trickFunction = trickOrTreat(true, null)
treatFunction()
trickFunction()
repeat(4) {
}
}
- 将对
treatFunction()函数的调用移动到repeat()函数的 Lambda 表达式中。
fun main() {
val treatFunction = trickOrTreat(false) { "$it quarters" }
val trickFunction = trickOrTreat(true, null)
repeat(4) {
treatFunction()
}
trickFunction()
}
- 运行代码。
"Have a treat"字符串应该打印四次。
5 quarters Have a treat! Have a treat! Have a treat! Have a treat! No treats!
7. 总结
恭喜您!您已经掌握了函数类型和 Lambda 表达式的基础知识。熟悉这些概念将有助于您进一步学习 Kotlin 语言。函数类型、高阶函数和速记语法的使用也能让您的代码更简洁、更易读。
总结
- Kotlin 中的函数是一等构造,可以像数据类型一样对待。
- Lambda 表达式提供了编写函数的速记语法。
- 您可以将函数类型传入其他函数。
- 您可以从其他函数返回函数类型。
- Lambda 表达式返回最后一个表达式的值。
- 如果在只有一个参数的 Lambda 表达式中省略了参数标签,则可以使用
it标识符来引用它。 - Lambda 可以内联编写,而无需变量名。
- 如果函数的最后一个参数是函数类型,则可以在调用函数时使用尾随 Lambda 语法将 Lambda 表达式移到最后一个圆括号之后。
- 高阶函数是指将其他函数作为参数或返回函数的函数。
repeat()函数是一个高阶函数,其工作方式类似于for循环。