Kotlin中的高阶函数,匿名函数、Lambda表达式
高阶函数、匿名函数与lambda 表达式
Kotlin 函数都是头等的,这意味着它们可以存储在变量与数据结构中、作为参数传递给其他高阶函数以及从其他高阶函数返回。可以像操作任何其他非函数值一样操作函数。
头等函数:头等函数(first-class function)是指在程序设计语言中,函数被当作头等公民。这意味着,函数可以作为别的函数的参数、函数的返回值,赋值给变量或存储在数据结构中。
高阶函数
高阶函数是将函数用作参数或返回值的函数。
//learnHighFun是一个高阶函数,因为他有一个函数类型的参数funParam,注意这里有一个新的名词,函数类型,函数在kotlin中也是一种类型。那他是什么类型的函数呢?注意(Int)->Int,这里表示这个函数是一个,接收一个Int,并返回一个Int类型的参数。
fun learnHighFun(funParam:(Int)->Int,param:Int){}
以上就是一个最简单的高阶函数了。了解高阶函数之前,显然,我们有必要去了解一下上面的新名词,函数类型
函数类型
如何声明一个函数类型的参数
在kotlin中,声明一个函数类型的格式很简单,在kotlin中我们是通过->符号来组织参数类型和返回值类型,左右是函数的参数,右边是函数的返回值,函数的参数,必须在()中,多个参数的时候,用,将参数分开。如下:
//表示该函数类型,接收一个Int类型的参数,并且返回值为Int类型
(Int)->Int
//表示该函数类型,接收两个参数,一个Int类型的参数,一个String类型的参数,并且返回值为Int类型
(Int,Stirng)->Int
那没有函数参数,和无返回值函数怎么声明?如下:
//声明一个没有参数,返回值是Int的函数类型,函数类型中,函数没有参数的时候,()不可以省略
()->Int
//明一个没有参数,没有返回值的函数类型,函数类型中,函数没有返回值的时候,Unit不可以省略
()->Unit
以上就是简单的函数类型的声明了。那么如果是一个高阶函数,它的参数类型也是一个高阶函数,那要怎么声明?比如以下的式子表示什么含义:
private fun learnHigh(funParams:((Int)->Int)->Int){}
//这里表示的是一个高阶函数learnHigh,他有一个函数类型的参数funParams。而这个funParams的类型也是一个高阶函数的类型。funParams这个函数类型表示,它接受一个普通函数类(Int)->Int的参数,并返回一个Int类型。这段话读起来确实很绕,但是你明白了这个复杂的例子之后,基本所有的高阶函数你都能看懂什么意思了。
//这里这个highParam的类型,就符合上面learnHigh函数所要接收的函数类型
fun highParam(param: (Int)->Int):Int{
return 1
}
讲了参数为函数类型的高阶函数,返回值类型为函数的高阶函数也基本参照上面的这些看就可以了。那么下一个问题来了,我是讲了这么多高阶函数,这么多函数类型的知识点。那么这些函数类型的参数要怎么传?换句话说,应该怎么样把这些函数类型的参数,传给的高阶函数?直接使用函数名可以吗?显然是不行的,因为函数名并不是一个表达式,不具备类型信息。那么我们这时候就需要一个单纯的方法引用表达式。
函数引用
在kotlin中,使用两个冒号的来实现对某个类的方法进行引用。 这句话包含了哪些信息呢?第一,既然是引用,那么说明是对象。也就是使用双冒号实现的引用也是一个对象。 它是一个函数类型的对象。第二,既然对象,那么他就需要被创建,也就是说,这里创建了一个函数类型的对象,这个对象是具有和这个函数功能相同的对象。还是举例子来说明一下上面两句话是什么意思:
fun testFunReference(){
funReference(1) //普通函数,直接通过函数名然后附带参数来调用。
val funObject = ::funReference //函数的引用,他本质上已经是一个对象了
testHighFun(funObject) //通过一个函数引用,将这个函数类型的对象,传递给高阶函数。所以高阶函数里面接收的参数本质上还是对象。
funObject.invoke(1) //等同于funReference(1)
funObject(1) //等同于funReference(1),等同于funObject.invoke(1)
}
fun funReference(param:Int){
//doSomeThing
}
fun testHighFun(funParam:(Int)->Unit){
//doSomeThing
}
//这是反编译出来的java代码
public final void testFunReference() {
this.funReference(1);
//val funObject = ::funReference 这句代码反编译出来就是这样的,可以看出这里是新创建了一个对象
KFunction funObject = new Function1((TestFun)this) {
// $FF: synthetic method
// $FF: bridge method
public Object invoke(Object var1) {
this.invoke(((Number)var1).intValue());
return Unit.INSTANCE;
}
public final void invoke(int p1) {
((TestFun)this.receiver).funReference(p1);
}
};
this.testHighFun((Function1)funObject);
((Function1)funObject).invoke(1);
((Function1)funObject).invoke(1);//funObject(1)最终是调用的funObject.invoke(1)
}
public final void funReference(int param) {
}
//可以看出这个testHighFun接收的是一个Function1类型的对象
public final void testHighFun(@NotNull Function1 funParam) {
Intrinsics.checkNotNullParameter(funParam, "funParam");
}
以上就是关于函数引用的知识点了。
理解了以上的用法,但是这种写法好像每次都需要去声明一个函数,那么有没有其他不需要重新声明函数的方法去调用高阶函数呢?那肯定还是有的,如果这都不支持那Kotlin的这个高阶函数这个特性不就有点鸡肋了吗?接下来就讲解另外两个知识点,kotlin中的匿名函数和Lambda表达式。
匿名函数
来讲匿名函数,看定义就知道这是一个没有名字的'函数',注意这里的'函数'这两个字是带有引号的。首先来看看怎么在高阶函数中使用吧。
//接着上面的例子讲
//除了这种通过引用对象调用testHighFun(funObject)的方法,还可以直接把一个函数当做这个高阶函数的参数。
val param = fun (param:Int){ //注意这里是没有函数名的,所以是匿名'函数'
//doSomeThing
}
testHighFun(param)
注意:通过之前的分析,我们可以知道,这个高阶函数testHighFun接收的参数是一个函数对象的引用,也就是说我们定义的val param是一个函数对象的引用,那么可以得出这个匿名'函数' fun(param:Int){},他的本质是一个函数对象。他并不是'函数'。我们可以看一下反编译出来的java代码
//param是一个Function1类型的对象的引用
Function1 param = (Function1)null.INSTANCE;
this.testHighFun(param);
所以记住一点,Kotlin中的匿名函数,它的本质不是函数。而是对象。它和函数不是一个东西,它是一个函数类型的对象。对象和函数,它们是两个东西。
Lambda表达式
Lambda 表达式的完整语法形式如下:
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
Lambda 表达式总是括在花括号中, 完整语法形式的参数声明放在花括号内,并有可选的类型标注, 函数体跟在一个 ->
符号之后。如果推断出的该Lambda 的返回类型不是 Unit
,那么该 Lambda 主体中的最后一个(或可能是单个) 表达式会视为返回值。
由于Kotlin中是支持类型推到的,所以以上的写法可以简化成如下两个格式:
val sum= { x: Int, y: Int -> x + y }
val sum: (Int, Int) -> Int = { x, y -> x + y }
在kotlin中还支持,如果函数的最后一个参数是函数,那么作为相应参数传入的 Lambda 表达式可以放在圆括号之外:
//比如我们上面的那个例子testHighFun,可以将lambda放到原括号之外
testHighFun(){
//doSomeThing
}
//如果该 lambda 表达式是调用时唯一的参数,那么圆括号可以完全省略:如下
testHighFun{
//doSomeThing
}
//一个 lambda 表达式只有一个参数是很常见的。
//如果编译器自己可以识别出签名,也可以不用声明唯一的参数并忽略 ->。 该参数会隐式声明为 it: 如下
testHighFun{
//doSomeThing
it.toString(it)
}
从 lambda 表达式中返回一个值
我们可以使用限定的返回语法从 lambda 显式返回一个值。 否则,将隐式返回最后一个表达式的值。参考官网的例子如下
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter
}
好了,以上就是Lambda的基本用法了。
讲了这么多,我们只是讲解了Lambda怎么使用,那么它的本质是什么?其实仔细思考一下上面的testHighFun可以传入一个Lambda表达式就可以大概知道,Lambda的本质也是一个函数类型的对象。这一点也可以通过发编译的java代码去看。
匿名函数与Lambda表达式的总结:
- 两者都能作为高阶函数的参数进行传递。
- 两者的本质都是函数类型的对象。
备注:以上就是我个人对高阶函数,匿名函数,Lambda表达式的理解,有什么不对的地方,还请各位大佬指正。