前语
前段时间一直在面试,某次面试,面试官看着我的简历说:“看你写的你很了解 kotlin 哦?那你说一说,为什么 kotlin 能够将函数作为参数和回来值,而 java 不行?”
我:“……”。
这次面试我连水都没喝一口就灰溜溜的走了。
回小黑屋的路上,突然想到,这玩意儿好像是叫 “高阶函数” 吧?好像我自己也经常用来着,咋就会啥也说不出来了呢?痛定思痛,赶紧恶补了一下相关的内容。
所以为什么 Kotlin 支撑函数作为参数呢?
其实翻看 Kotlin 官方文档 《High-order functions and lambdas》 一节,就会发现它的榜首段话就解释了为什么:
Kotlin functions are first-class, which means they can be stored in variables and data structures, and can be passed as arguments to and returned from other higher-order functions. You can perform any operations on functions that are possible for other non-function values.
To facilitate this, Kotlin, as a statically typed programming language, uses a family of function types to represent functions, and provides a set of specialized language constructs, such as lambda expressions.
因为在 Kotlin 中函数是头号公民,所以它们能够被储存在变量中、作为参数传递给其他函数或作为回来值从其他函数回来,就像操作其他一般变量一样操作高阶函数。
而 Kotlin 为了支撑这个特性,定义了一系列的函数类型而且提供一些特定的语言结构支撑(例如 lambdas 表达式)。
那么要怎样用呢?
高阶函数
首先,先看一段简略的代码:
fun getDataFromNet(onSuccess: (data: String) -> Unit) {
val requestResult = "我是从网络恳求拿到的数据"
onSuccess(requestResult)
}
fun main() {
getDataFromNet(
onSuccess = {data: String ->
println("获取到数据:$data")
}
)
}
运转代码,输出:
获取到数据:我是从网络恳求拿到的数据
下面咱们来解释一下这段代码是什么意思。
首先看 getDataFromNet 函数的参数 onSuccess
,嗯?这是个什么东西?
哈哈,看起来或许会觉得有点古怪,其实这儿的 onSuccess
也是一个函数,且带有参数 data: String
。
大致能够了解成:
fun onSuccess(data: String) {
// TODO
}
这么一个函数,不过实际上这个函数是并不叫 onSuccess
,咱们是只把这个函数赋值给了变量 onSuccess
。
从上面简略比如,咱们能够看出,假如要声明一个个高阶函数,那么咱们需求运用形如:
(arg1: String, arg2: Int) -> Unit
的函数类型来声明高阶函数。
基本方式便是一个括号 ()
+ ->
+ Unit
。
其中,()
内能够像一般函数一样声明接纳的参数,假如不接纳任何参数则能够只写括号:
() -> Unit
箭头则是固定表达式,不行省掉。
最终的 Unit
表明这个函数回来的类型,不同于一般函数,回来类型不行省掉,即便不回来任何数据也必须清晰声明 Unit
。
当一个一般函数接纳到一个作为参数的高阶函数时,能够经过 变量名()
或 变量名.invoke()
调用:
fun getDataFromNet(onSuccess: (data: String) -> Unit, onFail: () -> Unit) {
val requestResult = "我是从网络恳求拿到的数据"
// 调用名为 onSuccess 的高阶函数
onSuccess.invoke(requestResult)
// 也能够直接经过括号调用
onSuccess(requestResult)
// 调用名为 onFail 的高阶函数
onFail.invoke()
// 也能够直接经过括号调用
onFail()
}
下面再看一个有回来值的高阶函数的比如:
fun getDataFromNet(getUrl: (type: Int) -> String) {
val url = getUrl(1)
println(url)
}
fun main() {
getDataFromNet(
getUrl = {type: Int ->
when (type) {
0 -> "Url0"
1 -> "Url1"
else -> "Err"
}
}
)
}
上面的代码会输出:
Url1
将高阶函数作为函数回来值或许赋值给变量其实和上面大差不差,只要把一般用法中的回来值和赋值内容换成 函数类型 表明的高阶函数即可:
fun funWithFunReturn(): () -> Unit {
val returnValue: () -> Unit = { }
return returnValue
}
在实例化高阶函数时,高阶函数的参数需求运用形如
arg1: String , arg2: Int ->
的方式表明,例如:
fun getDataFromNet(onSuccess: (arg1: String, arg2: Int) -> Unit) {
// do something
}
fun main() {
getDataFromNet(
onSuccess = { arg1: String, arg2: Int ->
println(arg1)
println(arg2)
}
)
}
注意,这儿的参数名不一定要和函数中定义的一样,能够自己写。
假如参数类型能够推导出来,则能够不用声明类型:
fun getDataFromNet(onSuccess: (arg1: String, arg2: Int) -> Unit) {
// do something
}
fun main() {
getDataFromNet(
onSuccess = { a1, a2 ->
println(a1)
println(a2)
}
)
}
一起,假如某些参数没有运用到的话,能够运用 _
下划线代替:
fun getDataFromNet(onSuccess: (arg1: String, arg2: Int) -> Unit) {
// do something
}
fun main() {
getDataFromNet(
onSuccess = { a1, _ ->
println(a1)
}
)
}
用 lambda 表达式简化一下
在上面咱们举例的代码中,为了更好了解,咱们没有运用 lambda 表达式简化代码。
在实际运用过程中,咱们能够很多的运用 lambda 表达式来大大削减代码量,例如:
fun getDataFromNet(onSuccess: (data: String) -> Unit, onFail: () -> Unit) {
val requestResult = "我是从网络恳求拿到的数据"
if (requestResult.isNotBlank()) {
onSuccess(requestResult)
}
else {
onFail()
}
}
fun main() {
getDataFromNet(
onSuccess = {data: String ->
println("获取到数据:$data")
},
onFail = {
println("获取失利")
}
)
}
能够简化成:
fun main() {
getDataFromNet(
{
println("获取到数据:$it")
},
{
println("获取失利")
}
)
}
能够看到,假如高阶函数的参数只有一个的话,能够不用显式声明,默许运用 it
表明。
一起,假如一般函数的参数只有一个高阶函数,且坐落最右边,则能够直接提出来,不用写在括号内,并将括号省掉:
fun getDataFromNet(onSuccess: (data: String) -> Unit) {
// do something
}
fun main() {
// 这儿调用时省掉了 ()
getDataFromNet {
println(it)
}
}
即便一起有多个参数也不影响把最右边的提出来,只是此刻 ()
不能省掉:
fun getDataFromNet(arg: String, onSuccess: (data: String) -> Unit) {
// do something
}
fun main() {
getDataFromNet("123") {
println(it)
}
}
关于运用 lambda 后能把代码简化到什么程度,能够看看这篇文章举的安卓中的点击事情监听的比如:
从开端的
image.setOnClickListener(object: View.OnClickListener {
override fun onClick(v: View?) {
gotoPreview(v)
}
})
简化到只有一行:
image.setOnClickListener { gotoPreview(it) }
所以它有什么用?
更简洁的回调
在上文中,咱们举了运用 lambda 表达式后能够把点击事情监听省掉到只有一行的程度,可是这儿仅仅只是运用。
众所周知,安卓中写事情监听的代码需求一大串:
public interface OnClickListener {
void onClick(View v);
}
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
假如咱们运用高阶函数配合 lambda 则只需求:
var mOnClickListener: ((View) -> Unit)? = null
fun setOnClickListener(l: (View) -> Unit) {
mOnClickListener = l;
}
调用时也只需求:
setOnClickListener {
// do something
}
其实,咱们最开端举的封装网络恳求的比如便是一个很好的案例,假如不运用高阶函数,那么咱们为了实现网络恳求成功后的回调,还得额外多写一些接口类,得益于高阶函数,咱们只需求这样即可:
fun getDataFromNet(onSuccess: (data: String) -> Unit) {
val requestResult = "我是从网络恳求拿到的数据"
onSuccess(requestResult)
}
fun main() {
getDataFromNet {
println("获取到数据:$it")
}
}
让函数愈加多样
有时候,咱们或许会有一些比较特别的需求多重校验的嵌套场景,假如不运用高阶函数的话,或许需求这样写:
fun checkName(data: String): Boolean {
return true
}
fun checkAge(data: String): Boolean {
return true
}
fun checkGender(data: String): Boolean {
return true
}
fun checkId(data: String): Boolean {
return true
}
fun postData(data: String) {
}
fun main() {
val mockData = ""
if (checkName(mockData)) {
if (checkAge(mockData)) {
if (checkGender(mockData)) {
if (checkId(mockData)) {
postData(mockData)
}
}
}
}
}
假如运用高阶函数,则能够这么写:
fun checkName(data: String, block: (data: String) -> Unit) {
// if true
block(data)
}
fun checkAge(data: String, block: (data: String) -> Unit) {
// if true
block(data)
}
fun checkGender(data: String, block: (data: String) -> Unit) {
// if true
block(data)
}
fun checkId(data: String, block: (data: String) -> Unit) {
// if true
block(data)
}
fun postData(data: String) {
}
fun main() {
val mockData = ""
checkName(mockData) {
checkAge(it) {
checkGender(it) {
checkId(it) {
postData(it)
}
}
}
}
}
额……好像举的这个比如不太恰当,可是大概便是这么个意思。
更好的控制函数履行
在我写的项目中还有一个比上面一个愈加古怪的需求。
这个程序有个后台进程一直在别离恳求多个状况,且每个状况回来的数据类型都不同,咱们需求别离将这些状况悉数恳求完成后打包成一个独自的数据,而且这些状况或许并不需求悉数都恳求,需求依据状况实时调整恳求哪些状况,更烦的是,会有一个中止状况,假如收到中止的指令,咱们必须立即中止恳求,所以不能等待所有恳求完成后再中止,必需要立即中止当时所在的恳求。假如直接写你会怎样写?
听见都头大了是吧,可是这个便是我之前写工控程序时经常会遇到的问题,需求有一个后台进程实时轮询不同从站的不同数据,而且因为串口通信的特性,假如此刻有新的指令需求下发,必须立即中止轮训,优先下发新指令。
所以我是这样写的:
fun getAllStatus(needRequestList: List<Any>, isFromPoll: Boolean = false): StatusData {
val fun0: () -> ResponseData.Status1 = { syncGetStatus1() }
val fun1: () -> ResponseData.Status2 = { syncGetStatus2() }
val fun2: () -> ResponseData.Status3 = { syncGetStatus3() }
val fun3: () -> ResponseData.Status4 = { syncGetStatus4() }
val fun4: () -> ResponseData.Status5 = { syncGetStatus5() }
val fun5: () -> ResponseData.Status6 = { syncGetStatus6() }
val fun6: () -> ResponseData.Status7 = { syncGetStatus7() }
val fun7: () -> Int = { syncGetStatus8() }
val fun8: () -> Int = { syncGetStatus9() }
val funArray = arrayOf(
fun0, fun1, fun2, fun3, fun4, fun5, fun6, fun7, fun8
)
val resultArray = arrayListOf<Any>()
for (funItem in funArray) {
if (isFromPoll && (isPauseNwPoll() || !KeepPolling)) throw IllegalStateException("轮询被中止")
if (funItem in needRequestList) resultArray.add(funItem.invoke())
}
// 后面的省掉
}
能够看到,咱们把需求恳求的函数悉数作为高阶函数存进 funArray
数组中,然后遍历这个数组开端挨个履行,而且每次履行时都要判别这个恳求是否需求履行,以及当时是否被中止恳求。
得益于高阶函数的特性,咱们能够便利的控制每个函数的履行机遇。
总结
因为我讲的比较粗浅,读者或许看起来会一头雾水,所以这儿推荐结合下面列出的参考资料的文章一同看,一起自己上手敲一敲,就能很好的了解了。
参考资料
- High-order functions and lambdas
- 头号函数
- Kotlin Jetpack 实战 | 04. Kotlin 高阶函数
本文正在参与「金石方案」