Kotlin笔记 07 - 高阶函数
函数类型
1 | // (Int, Int) ->Float 这就是 add 函数的类型 |
将函数的“参数类型”和“返回值类型”抽象出来后,就得到了“函数类型”。
(Int, Int) ->Float 就代表了参数类型是两个 Int,返回值类型为 Float 的函数类型。
函数的引用
1 | // 函数赋值给变量 函数引用 |
高阶函数
高阶函数是将函数用作参数或返回值的函数。
如果我们将 Android 里点击事件的监听用 Kotlin 来实现的话,它其实就是一个典型的高阶函数。
1 | // 函数作为参数的高阶函数 |
SAM 转换
SAM 是 Single Abstract Method 的缩写,意思就是只有一个抽象方法的类或者接口。但在 Kotlin 和 Java 8 里,SAM 代表着只有一个抽象方法的接口。只要是符合 SAM 要求的接口,编译器就能进行 SAM 转换,也就是我们可以使用 Lambda 表达式,来简写接口类的参数。
Lambda 表达式引发的 8 种写法
第 1 种写法
这是原始代码,它的本质是用 object 关键字定义了一个匿名内部类:
1 | image.setOnClickListener(object: View.OnClickListener { |
第 2 种写法
在这种情况下,object 关键字可以被省略。这时候它在语法层面就不再是匿名内部类了,它更像是 Lambda 表达式了,因此它里面 override 的方法也要跟着删掉:
1 | image.setOnClickListener(View.OnClickListener { v: View? -> |
上面的View.OnClickListener被称为 SAM Constructor(SAM 构造器),它是编译器为我们生成的。
第 3 种写法
由于 Kotlin 的 Lambda 表达式是不需要 SAM Constructor 的,所以它也可以被删掉:
1 | image.setOnClickListener({ v: View? -> |
第 4 种写法
由于 Kotlin 支持类型推导,所以 View 可以被删掉:
1 | image.setOnClickListener({ v -> |
第 5 种写法
当 Kotlin Lambda 表达式只有一个参数的时候,它可以被写成 it:
1 | image.setOnClickListener({ it -> |
第 6 种写法
Kotlin Lambda 的 it 是可以被省略的:
1 | image.setOnClickListener({ |
第 7 种写法
当 Kotlin Lambda 作为函数的最后一个参数时,Lambda 可以被挪到外面:
1 | image.setOnClickListener() { |
第 8 种写法
当 Kotlin 只有一个 Lambda 作为函数参数时,() 可以被省略:
1 | image.setOnClickListener { |
动画显示演变过程:
函数类型、高阶函数以及 Lambda 表达式三者之间的关系:
难点:带接收者的函数类型
以apply函数为例
不用 apply:
1 | if (user != null) { |
使用 apply:
1 | user?.apply { |
反推apply函数:
1 | // apply 肯定是个函数,所以有 (),只是被省略了 |
apply 其实是接收了一个 Lambda 表达式:{ this: User -> … }。那么现在,我们就尝试来实现这个 apply 方法:
1 | fun User.apply(self: User, block: (self: User) -> Unit): User{ |
由于 Kotlin 里面的函数形参是不允许被命名为 this 的,因此我这里用的是 self。另外,这里我们自己写出来的 apply,仍然还要通过 self.name 这样的方式来访问成员变量,但 Kotlin 的语言设计者能做到这样:
1 | // 改为this 改为this |
从上面的例子能看到,我们反推的 apply 实现会比较繁琐:需要我们传入 this:user?.apply(this = user)。需要我们自己调用:block(this)。因此,Kotlin 就引入了带接收者的函数类型,可以简化 apply 的定义:
1 | // 带接收者的函数类型 |
上面的 apply 方法是不是看起来就像是在 User 里,增加了一个成员方法 apply()?
1 | class User() { |
所以,从外表上看,带接收者的函数类型,就等价于成员方法。但从本质上讲,它仍是通过编译器注入 this 来实现的。
那么,带接收者的函数类型,可看成扩展函数,从语法层面讲,扩展函数就相当于成员函数。