阅读 163
Kotlin中的高阶函数,匿名函数、Lambda表达式

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表达式的总结:

  1. 两者都能作为高阶函数的参数进行传递。
  2. 两者的本质都是函数类型的对象。

备注:以上就是我个人对高阶函数,匿名函数,Lambda表达式的理解,有什么不对的地方,还请各位大佬指正。

参考文章:

  1. juejin.cn/post/684490…
  2. Kotlin核心编程
  3. kotlin官网
文章分类
Android
文章标签