一天一个小知识:KT高阶函数

 

让我们从匿名函数聊起

我们听说过有匿名类,那作为一等公民的函数就也会有匿名函数

什么是一等公民?

我们知道Java的一等公民是类,就连一个普通的程序入口也要用类包一下。而kotlin中除了类是一等公民,函数也是一等公民,所以函数也是一种类型,他也有,那函数类型的值是什么呢?当然就是我们定义的函数啦。可以理解为函数类型是对函数的抽象。将一类函数归为一类。举个例子比如 ()→ Unit 所有无参无返回值的函数都属于这个类型。

理解函数:

函数有什么用?我们最容易想到的就是代码复用。那在面向对象的语言中,函数还有什么用呢,如果在类的里面,那就是函数(方法)是对象和外界交流的接口(广义接口),在 《Java编程思想》中 作者将函数(方法)的调用比作给一个对象发消息告诉这个对象应该干什么事情。在《Java编程思想》中作者建议我们将对象看作服务的提供者。

而对于函数和方法之间的区别 就是有无接收者

接收者?

我们现在已经知道了,方法是对象给外界提供的一个服务,而接收者就是我们的某个对象,我们调用方法的时候,就是告知这个对象,我们需要这个服务。

方法就是带接收者的函数,换句话说:我们发送的消息(调用函数)是需要一个对象来执行的。

匿名函数:

1fun main() {
2    val func = fun() {
3        println("pandaer")
4    }
5    func()
6}

fun() { println("pandaer") } 这就是匿名函数,和普通的函数比起来就是没有函数名字。

lambda表达式就是匿名函数的语法糖

 1fun main() {
 2    val func = fun(i : Int) {
 3        println("pandaer $i")
 4    }
 5    func(10)
 6}
 7----------------------------------
 8fun main() {
 9    val func = { i : Int ->
10        println("pandaer $i")
11    }
12    func(10)
13}

观察发现,lambda表达式就是进一步简化了代码,去掉了意义不大的 fun 将参数列表 和函数体用 → 隔开

高阶函数

什么是高阶函数呢?

参数类型或者返回值类型有函数类型的函数叫做高阶函数

 1fun main() {
 2    f1 {
 3        println(it)
 4    }
 5}
 6------------------------------------
 7// 高阶函数
 8fun f1(block:(Int) -> Unit) {
 9    block(1)
10}

我们来从服务的提供者来理解一下高阶函数:

 1class Service {
 2
 3        //高阶函数
 4    fun f2(block: (String) -> Unit) {
 5        block("pandaer")
 6    }
 7
 8        //普通函数
 9    fun f3(str : String) {
10        println("pandaer")
11    }
12
13}

我们知道普通函数,通过外部调用时传递数据,执行具体的逻辑,而高阶函数则是通过外部地定义要执行具体的逻辑,内部调用时传递数据

所以我们再来定义定义函数类型:把逻辑抽象成了类型

函数引用

既然函数是一种类型,那么我们就可以把函数赋值给一个同类型的变量,这就是函数引用。

 1class Service {
 2                    //类似于这样
 3    val func = this::f3
 4
 5    fun f2(block: (String) -> Unit) {
 6        block("pandaer")
 7    }
 8
 9    fun f3(str : String) {
10        println("pandaer")
11    }
12
13}

限定作用域的高阶函数

我们普通的高阶函数并没有限定作用域。

什么是限定作用域?

这个高阶函数的作用域不是全局的,而是某个类的。就相当于把作用域从全局限定到了一个类的作用域上。换句话:这个高阶函数似乎变成了类的一个成员方法

1fun main() {
2    "XiXI".print()
3}
4
5fun String.print(block: (String) -> Unit = ::print) {
6    block(this.lowercase())
7    block(lowercase())
8}

这样子就可以将这个方法限定在了String类中了,另外啊由于这个高阶函数作用在String类的作用域中,那么可以访问同为这个String类作用域中的全部方法(其实只能是公开本质和扩展方法一样的)

其实限定作用域的高阶函数不就是扩展函数的高阶函数吗?😄

我们再来谈谈带接受者函数类型的本质

 1fun main() {
 2        //这里
 3    "XiXI".print()
 4
 5        //这里
 6    val func = String::print
 7    func("PANDAER"){
 8        println(it)
 9    }
10}
11
12fun String.print(block: (String) -> Unit = ::print) {
13    block(this.lowercase())
14    block(lowercase())
15}

我们用func 变量引用了我们定义的高阶扩展函数,然后只要拿着我们的应用就可以和函数一样想怎么调用就怎么调用,而不是一定要限定在String对象上了

啊!这是为什么呢?

这就是高阶函数的实现底层决定的 实际上接收者会作为函数的第一个参数被传进去

 1fun main() {
 2
 3    val f1 : String.(Int) -> Unit = String::num
 4    val f2 : (String,Int) -> Unit = String::num
 5
 6
 7}
 8
 9fun String.num(sum : Int) {
10    println(sum)
11}

其实这已经可以证明了 String.(Int) -> Unit(String,Int) -> Unit 等价

我们再来看看反编译的结果吧

 1public final class OverLambada1Kt {
 2   public static final void main() {
 3      Function2 f1 = (Function2)null.INSTANCE; //这个不用管,反编译存在损失
 4      Function2 f2 = (Function2)null.INSTANCE;
 5   }
 6
 7   // $FF: synthetic method
 8   public static void main(String[] var0) {
 9      main();
10   }
11
12    // 注意这个
13   public static final void num(@NotNull String $this$num, int sum) {
14      Intrinsics.checkNotNullParameter($this$num, "$this$num");
15      System.out.println(sum);
16   }
17
18}

可以看到反编译出来的本质就是将接收者当作第一个参数传递了进去

我们的f1 和 f2 都是 Function2 这个类型

1//比较底层的函数类型结果还是一个接口
2public interface Function2<in P1, in P2, out R> : Function<R> {
3    /** Invokes the function with the specified arguments. */
4    public operator fun invoke(p1: P1, p2: P2): R
5}

还是那句话 理想是丰满的,现实是骨感的,kotlin的设计者引入的函数类型,但是JVM并不认识函数类型,所以实现层面上还是用的接口

我们现在讲的都是顶层的高阶扩展函数,那么我们来看看类中高阶扩展函数实现原理是怎样的

 1public final class Service {
 2   @NotNull
 3   private final KFunction func = new Function1((Service)this) {
 4      // $FF: synthetic method
 5      // $FF: bridge method
 6      public Object invoke(Object var1) {
 7         this.invoke((String)var1);
 8         return Unit.INSTANCE;
 9      }
10
11      public final void invoke(@NotNull String p1) {
12         Intrinsics.checkNotNullParameter(p1, "p1");
13         ((Service)this.receiver).f3(p1);
14      }
15   };
16
17   @NotNull
18   public final KFunction getFunc() {
19      return this.func;
20   }
21
22        //看过来
23   public final void f2(@NotNull Function1 block) {
24      Intrinsics.checkNotNullParameter(block, "block");
25            block.invoke("pandaer");
26   }
27
28   public final void f3(@NotNull String str) {
29      Intrinsics.checkNotNullParameter(str, "str");
30      String var2 = "pandaer";
31      System.out.println(var2);
32   }
33}

高阶函数在类里面就变成了成员方法了

 1//类中定义
 2fun String.f2(block: (String) -> Unit) {
 3        block("pandaer")
 4        println(this)
 5    }
 6------------------------------------
 7//反编译后
 8public final void f2(@NotNull String $this$f2, @NotNull Function1 block) {
 9      Intrinsics.checkNotNullParameter($this$f2, "$this$f2");
10      Intrinsics.checkNotNullParameter(block, "block");
11      block.invoke("pandaer");
12      System.out.println($this$f2);
13   }

无论怎么样接收者类型都会被当作参数被传递进去,因为在类中定义的带接收者类型的函数,所以这个f2 就有两个接收者,一个隐式的Service一个显式的String,但是kt的语法并不支持我们引用有两个接收者函数类型,所以这个函数被关进了Service中,只能在Service中出不来了。

总结一下:

高阶函数:具体逻辑交给外部,数据内部管理

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值