kotlin高阶函数 & lambda表达式

  • kotlin高阶函数 和 lambda表达式

  • 资料来源:

    https://www.kotlincn.net/docs/reference/lambdas.html
    https://kaixue.io/kotlin-lambda/

  • 更新

    1
    20.06.20 初始化

导语

当接手 c 项目时,第一件事情就是把项目拆成传函数指针+调用.面对各种新的需求,一个一个改函数太痛苦了.

没想打这份痛苦延续到了 java.当然可以用接口/回调曲线救国,但总是怀念 python 的高阶函数,今年决定学不学 kotlin 时,看到支持高阶函数,没啥犹豫了.

kotlin 的 lambda 匿名函数 和高阶函数,其实并不复杂,建议先翻看一下 Kotlin 的 Lambda 表达式,大多数人学得连皮毛都不算.

高阶函数

高阶函数简单来说就是参数和返回值可以是另一个函数的这一类函数的统称.

看到 kt 支持高阶函数第一个疑问是要怎么区分不同的函数? kt 的解法很简单,java 中针对函数重载是通过函数的参数和返回值类型来区分的,那我声明一个高阶函数也能通过参数与返回值类型来区分.

另一个问题与 kt 协程的实现类似,kt 在 java/Android 编译后还是跑在 jvm,jvm 不支持高阶函数,kt 怎么办到的? 同样的 kt 对高阶函数的支持是语法层面的,kt 的高阶函数能接收和返回的同样只能是对象,不过 kt 可以对函数进行包装,把函数包装成了一个与原函数功能相同的对象,然后传入或者返回这个对象.但是 kt 的语法糖让高阶函数在语法上看起来像是直接传入或返回函数本身,降低学习成本.

先来看一个 kt 高阶函数的示例,

1
2
3
4
5
6
7
8
9
10
11
12
13
fun a(b: (Int) -> String): String {
return b(1)
}

fun b(param: Int): String {
...
}

fun c(param: Int): (Int) -> Unit {
...
}

a(::b)

参数声明了一个(入参为 Int 返回值为 String )的 函数类型 参数.注意这个依旧是参数,只不过是个函数类型.

传入函数时,需要通过 ::b 将函数b 传入,实际上 ::b 是一个和函数 b 功能相同的函数类型对象.在函数内部可以像函数一样 b(1)调用.但是还可以通过 b.invoke(1) 调用,但是函数b 不能这样调用.说明在 a 内部的 b 是一个函数类型的对象,而不是一个函数.

kt 的函数没有返回值时会有一个默认的返回值 Unit ,当函数作为参数时要显式的声明 Unit.

当需要传入一个暂停函数时,需要在原有入参出参类型声明前加上 suspend.

1
2
fun dbMethod(type: String): suspend (MutableCollection<DB>) -> Unit {...}
suspend fun dbAlarm(list: MutableCollection<DB>) {...}

lambda

kt 的 lambda 是真正的 lambda 比 java8 的有限支持好多了.话说现在都到 java14 了,android 上还是 java8 应该不会再跟进了吧.

  • lambda 表达式总是括在大括号中,完整语法形式的参数声明放在花括号内,并有可选的类型标注,函数体跟在一个 -> 符号之后.val sum = { x: Int, y: Int -> x + y }
  • lambda 表达式没有参数,可以省略 ->.
  • lambda 只有一个入参可以省略不写,在 lambda 内使用 it 访问.
  • lambda 是函数最后一个参数,可以将大括号放在小括号后面.
  • lambda 是函数唯一的参数,可以省略小括号
  • lambda 参数数量上限是22个,因为只有 kotlin 标准库才可以使用 kotlin 的包名,超过22个需要只能自行添加 java 类实现.
  • lambda 最后一句代码就是 lambda 的返回类型.没错 lambda 内最后不要直接使用 return,这样会把 lambda 外层的函数也直接结束掉.但是 lambda 现在也支持 return@xxx 这样和不使用 return 是一样的.

与 java 的 lambda

java8 中引入的 lambda 支持实际上单抽象方法接口-SAM 接口.这样的接口 java8 允许你使用 lambda 简化创建过程.

但是 kotlin 中的 lambda 是真正的函数类型的对象,和 java8 的 lambda 有本质区别,kotlin 更希望你使用函数参数类型.

按理来说是 kt 是不支持 SAM 使用 lambda 简化的,但和 java 交互的时候,Kotlin 是支持这种用法的.

在 kt 1.4 版本中增加了对 SAM 的 lambda 简化支持,但是 Android 中我还没升级到 1.4,暂且还没体验.

匿名函数

匿名函数与普通函数的区别就是没有函数名,从上文也能知道,区别一个函数入参类型和返回值类型足够了,省略函数名的匿名函数有时能简略不少.

1
2
3
4
5
6
a(fun(param: Int): String {
return param.toString()
})
val d = fun(param: Int): String {
return param.toString()
}

注意匿名函数依旧是个函数,不能像 lambda 那样把入参写在圆括号外面.

另一个区别是不带标签的 return 语句的返回结果.lambda 总是在外层函数中返回,lambda 表达式中的 return 可以把外层函数结束掉.但匿名函数中的 return 将只将匿名函数自身返回.

内联函数

简单来说,高阶函数在带来代码简化的同时,也带来了额外的开销.毕竟无论入参出参都要创建函数类型的对象,如果大量使用高阶函数,这样一次性使用的函数类型对象会大量创建.

inlin 关键词可以消除这样的问题,但是内联函数还有其他内容,暂且不表,新开一篇或者再续写.