一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情
前言
这是kotlin重学系列的第一篇,关于高阶函数,耐心读完本文你将学会:
- 理解kotlin高阶函数的本质
- 搞懂kotlin的lambda表达式
- 了解kotlin的lambda和java lambda的区别
为什么把高阶函数作为kotlin重学系列的的第一篇?没错,就是因为它重要!!!如果你没掌握高阶函数,kotlin很多语法看起来就会很难受,甚至完全看不懂,例如kotlin扩展,协程,DSL等等,这些几乎都跟高阶函数有密切联系,可以说不会高阶函数,和写java没区别
函数类型
java中有函数类型么?没有,java的数据类型只包括int,long等基本数据类型以及String,数组等引用类型
那kotlin为什么又引入一个函数类型?有什么作用?
假设现在有这样一个逻辑:我们自己定义了一个函数,但是这个函数里面有一部分逻辑想要调用者来定义,也就是调用者传递一段代码逻辑进来,这个用java来实现有办法么?
有,利用接口,例如监听点击事件SetOnClickListener
方法,它接收的就是一个接口,我们匿名实现OnClickListener
接口,就实现了把一段代码逻辑传递了过去
因为java不支持函数类型,只能把这个函数用接口再包一层,所以这属于是一种曲线救国的方式,平时有IDE的自动补全功能可能也都觉得无所谓,但我们为了实现这样很常见的逻辑,每次都要定义一个接口,然后再去实现它,看着那么多冗余的代码多少有点儿蛋疼吧
所以kotlin中引入了函数类型就是为了解决这类问题,让函数也可以直接作为另外一个函数的参数或者返回值
函数类型的表示
因为不同函数之间,主要就是入参和返回值的不同,所以函数类型的表示公式如下:
(参数)->返回值
箭头左边的括号里表示的是入参的类型信息(有多个参数用“,”分割),箭头右边就是返回值类型信息,看栗子
// 表示的函数有两个Int类型的参数,返回值也是Int
(Int,Int)->Int
// 函数没有入参,返回值是空的
()->Unit
复制代码
高阶函数
定义:如果一个函数的参数或者返回值是函数类型,那么这个函数就称之为高阶函数
例如
// 定义一个函数funA,接收一个函数类型的参数
fun funA(funParam: (Int) -> Int) {
funParam(1)
}
// 定义一个函数funB,该函数入参为一个Int,返回值为一个Int
fun funB(param: Int): Int {
return param+1
}
// 调用
funA(::funB)
复制代码
如上代码,函数funA
就是一个高阶函数,因为他的入参包含了一个函数类型
又定义了一个函数funB
,入参和返回值正好满足funA
接收的函数类型,所以就可以把funB
这个函数传递给funA
注意我们在调用的时候,不是直接把funB这个名字标识符传给funA,而是在前面加了一个::,那这个“::”是干什么的呢?
这个双冒号的写法叫做函数引用,也就是说::funB这个是指向函数funB的,为什么又需要整这些花里胡哨的,因为函数他毕竟本质还是一个函数,函数之所以可以作为参数传递给另外一个函数,他的本质是kotlin可以把函数包装为一个对象,也就是说::funB是一个函数类型的对象
不过实际开发中我目前很少用这种方式,大多数都是用匿名函数和lambda,这个才是重点,下面马上会讲到
匿名函数
没有名字的函数就叫匿名函数,还是上面那个demo,我们完全可以不单独定义一个函数funB
,而是直接用匿名函数
funA(fun(param:Int):Int{
return param+1
})
// 或者可以直接把匿名函数赋值给一个变量
val d = fun(param: Int): Int {
return param+1
}
funA(d)
复制代码
我们看到这里匿名函数可以直接传递给函数,也可以直接赋值给变量,所以它跟上面的::funB
就是等价的,也就是说匿名函数实质上是一个函数类型的对象
lambda
上面使用匿名函数的方式看着还是有点别扭,总感觉很臃肿,而kotlin的宗旨就是简洁,所以就有了lambda,像下面这样
funA({param:Int ->
param+1
})
复制代码
lambda在匿名函数的基础上又省略了一些代码,包括省略了fun关键字,把参数也直接放在了大括号里面,这样看起来简洁了不少,不过lambda的简洁还不远不如此,下面会一步步简化
先看看lambda的标准格式
{参数->函数体}
lambda的内容都在一个大括号里面,用箭头分割成两部分,箭头左边是参数,箭头右边是函数体,如果没有参数,箭头以及左边的就直接省略不写
// 没有参数直接省略箭头和左边的内容
val lambda1 = { println("无参数的lambda") }
val lambda2 = { param: Int ->
println("带参数的lambda$param")
}
// lambda的函数体的最后一行的结果表示的就是返回值
val lambda3 = { param: Int ->
println("带参数的lambda$param")
param
}
复制代码
上面的栗子展示了有参数和没有参数的lambda情况,另外值得注意的是lambda的最后一行的执行结果就是这个函数的返回值,例如上述lambda2的最后一行为println("带参数的lambda$param")
,所以他的返回值类型为Unit,lambda3的最后一行是param
,所以他的返回值就是这个param
,也就是Int类型的
下面继续看看lambda表达式还能怎么简写
1.如果lambda是函数的最后一个参数,那么就可以把lambda写在括号外面
funA(){ param: Int ->
param + 1
}
复制代码
2.如果函数只有lambda一个参数,括号里面就是空的,写了有何用,省略
funA{ param: Int ->
param + 1
}
复制代码
3.kotlin支持类型推导,所以lambda中的参数类型也能省略
funA(){ param ->
param + 1
}
复制代码
4.如果lambda只有一个参数,也可以省略
funA {
it + 1
}
复制代码
如果lambda只有一个参数的时候,会默认提供一个名为it的参数来表示这个入参,在lambda内可以直接使用,不用声明
为什么越来越任性,啥都可以省略,那是因为kotlin坚持只要能推断出来的地方,能不写的代码就不写,例如这个地方的参数为什么可以省略,就是因为他能推导出来,从哪儿推导呢?肯定是函数定义的地方,函数funA在定义的时候就声明了这个函数的类型,包括参数类型,返回类型
那如果lambda有多个参数了?
fun funAA(funParam:(Int,Int)->Int){
funParam(1,1)
}
// 必须写上参数,如果参数没有被使用,可以使用_代替
funAA { _, i2 -> i2+1 }
复制代码
多个参数就需要指明参数的名字了,如果参数没有使用,就可以使用_代替
kotlin的lambda和java的lambda的对比
java的lambda是在java8引入的,是针对单抽象SAM(Single Abstract Method)接口的简化使用,例如Runnable
// 原始写法
new Thread(new Runnable(){
@Override
public void run() {
}
});
// 引入lambda之后
new Thread(() -> {
int i = 1;
});
复制代码
而kotlin的lambda是一个函数对象,划重点它是一个对象,是一个函数类型的对象,所以它可以作为参数直接赋值给别的变量,而java中的lambda是做不到的,他只能用在单抽象接口的地方,只是一种写法上的变化,本质并没有改变,还是匿名内部类
在kotlin中,针对这种SAM接口,也同样做了兼容转换,直接把这种使用SAM接口的函数变成了一个高阶函数,所以也可以直接应用kotlin的lambda
Thread { val i = 1 }
复制代码
以上就是kotlin高阶函数的全部内容,其实没有什么难度,只是刚开始学可能有点不太习惯,但还是要说kotlin的高阶函数非常重要,尤其是lambda,只要平时多写,都很简单
参考大佬文章: