Lambda表达式和函数类型

将函数储存在变量中

如需将函数作为值引用,您需要使用函数引用运算符 (::)。语法如下图所示:

示例

fun main() {
    val trickFunction = ::trick
}

fun trick() {
    println("No treats!")
}

使用 lambda 表达式重新定义函数

此时若需引用:

fun main() {
    val trickFunction = trick 	
    trick() 					
}

val trick = {
    println("No treats!")
}
fun main() {
    val trickFunction = trick	//没有::
    trick()						//调用函数
    trickFunction()
}

val trick = {
    println("No treats!")
}

输出应为

No treats!
No treats!

将函数用作数据类型

如果要指定函数参数的类型或返回值类型,则需要了解用于表达函数类型的语法。函数类型由一组圆括号组成,其中包含可选的参数列表、-> 符号和返回值类型。

val treat: () -> Unit = {
    println("Have a treat!")
}

尝试调用:

fun main() {
    val trickFunction = trick
    trick()
    trickFunction()
    treat()
}

输出应为

No treats!
No treats!
Have a treat!

将函数用作返回值类型

尝试创建函数:

fun trickOrTreat(isTrick: Boolean): () -> Unit {
    if (isTrick) {
        return trick
    } else {
        return treat
    }
}
val trick = {
    println("No treats!")
}

val treat = {
    println("Have a treat!")
}

从主函数调用:

fun main() {
    val treatFunction = trickOrTreat(false)
    val trickFunction = trickOrTreat(true)
}

您的输出应为

Have a treat!
No treats!

将一个函数作为参数传递到另一个函数

在世界上某些过万圣节的地区,孩子们会收到零钱(而非糖果),或者既能收到零钱,也能收到糖果。您将修改 trickOrTreat() 函数,以允许提供函数代表的其他招待内容作为参数

当您为接受参数的函数编写 lambda 表达式时,系统会按参数出现的先后顺序为参数命名。参数名称列在左大括号后面,各名称之间以英文逗号隔开。箭头 (->) 将参数名称与函数正文隔开。语法如下图所示:

trickOrTreat修改为:

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 cupcake: (Int) -> String = { //移除 quantity 参数和 -> 符号。这里用不到它们
    	"Have a cupcake!"
	}

    val treatFunction = trickOrTreat(false)
    val trickFunction = trickOrTreat(true)
    treatFunction()
    trickFunction()
}

运行您的代码。仅当 isTrick 参数设置为 false 实参时,系统才会调用 extraTreat() 函数,因此输出会包含 5 个 25 美分硬币,但不包含纸杯蛋糕。

5 quarters
Have a treat!
No treats!

可为 null 的函数类型

与其他数据类型一样,函数类型可声明为可为 null。在这些情况下,变量可以包含函数,也可以为 null

如需将函数声明为可为 null,请用圆括号括住函数类型,并在右圆括号外后接 ? 符号。

例如,如果您想让 () -> String 类型可为 null,则将其声明为 (() -> String)? 类型。语法如下图所示:

extraTreat 参数设置为可为 null,这样您就不必在每次调用 trickOrTreat() 函数时都提供 extraTreat() 函数:

  1. extraTreat 参数的类型更改为 (() -> String)?

    fun trickOrTreat(isTrick: Boolean, extraTreat: ((Int) -> String)?): () -> Unit {
    
  2. 将对 extraTreat() 函数的调用修改为使用 if 语句,以便仅在该函数为非 null 时才调用该函数。现在,trickOrTreat() 函数应如以下代码段所示:

    fun trickOrTreat(isTrick: Boolean, extraTreat: ((Int) -> String)?): () -> Unit {
        if (isTrick) {
            return trick
        } else {
            if (extraTreat != null) {
                println(extraTreat(5))
            }
            return treat
        }
    }
    
  3. 移除 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()
    }
    

最终运行输出应为:

5 quarters
Have a treat!
No treats!

妙哉

使用简写语法编写 lambda 表达式

省略参数名称

在编写 coins() 函数时,您为函数的 Int 参数明确声明了名称 quantity。不过我们有更简单的方法,可以不用显式声明

如图:

Kotlin 中,单参数的 lambda 默认使用 it 作为参数名,因此你可以选择使用 $it 来引用传入的参数

即:可将coin函数更新为:

val coins: (Int) -> String = {
    "$it quarters"
}

将 lambda 表达式直接传入函数

目前仅在一个位置使用了 coins() 函数。如果您只需将 lambda 表达式直接传入 trickOrTreat() 函数,而无需先创建变量呢?

流程如下:

尝试移除coins函数,并将语法替换至传参:

fun main() {
    val treatFunction = trickOrTreat(false, { "$it quarters" })
    val trickFunction = trickOrTreat(true, null)
    treatFunction()
    trickFunction()
}

运行应仍为

5 quarters
Have a treat!
No treats!

使用尾随 lambda 语法

(其实是传入参数的简化版)

逻辑如下:

即可以在 treatFunction 变量中,将 lambda 表达式 {"$it quarters"} 移到对 trickOrTreat() 的调用中的右圆括号后面。

val treatFunction = trickOrTreat(false) { "$it quarters" }

运行代码应该如常

使用 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 循环类似。语法如下图所示:

示例:

fun main() {
    val treatFunction = trickOrTreat(false) { "$it quarters" }
    val trickFunction = trickOrTreat(true, null)
    repeat(4) {
        treatFunction()
    }
    trickFunction()
}

输出为:

5 quarters
Have a treat!
Have a treat!
Have a treat!
Have a treat!
No treats!

总结

  • Kotlin 中的函数是一级结构,可以视为数据类型。
  • lambda 表达式提供了一种用于编写函数的简写语法。
  • 您可以将函数类型传入其他函数。
  • 对于一个函数类型,您可以从另一个函数返回它。
  • lambda 表达式会返回最后一个表达式的值。
  • 如果只有一个参数的 lambda 表达式中省略了某个参数标签,系统会使用 it 标识符来引用它。
  • lambda 可以内嵌方式编写,无需使用变量名称。
  • 在您调用某个函数时,如果该函数的最后一个参数是函数类型,您可以使用尾随 lambda 语法将 lambda 表达式移至最后一个圆括号后面。
  • 高阶函数是指接受函数作为参数或返回函数的函数。
  • repeat() 函数是一个高阶函数,其工作方式与 for 循环类似。

更多推荐