函数类型

1
2
3
//         (Int,  Int) ->Float 这就是 add 函数的类型
// ↑ ↑ ↑
fun add(a: Int, b: Int): Float { return (a+b).toFloat() }

将函数的“参数类型”和“返回值类型”抽象出来后,就得到了“函数类型”。
(Int, Int) ->Float 就代表了参数类型是两个 Int,返回值类型为 Float 的函数类型。

函数的引用

1
2
3
// 函数赋值给变量                    函数引用
// ↑ ↑
val function: (Int, Int) -> Float = ::add

高阶函数

高阶函数是将函数用作参数或返回值的函数。
如果我们将 Android 里点击事件的监听用 Kotlin 来实现的话,它其实就是一个典型的高阶函数。

1
2
3
//                      函数作为参数的高阶函数
// ↓
fun setOnClickListener(l: (View) -> Unit) { ... }

SAM 转换

SAM 是 Single Abstract Method 的缩写,意思就是只有一个抽象方法的类或者接口。但在 Kotlin 和 Java 8 里,SAM 代表着只有一个抽象方法的接口。只要是符合 SAM 要求的接口,编译器就能进行 SAM 转换,也就是我们可以使用 Lambda 表达式,来简写接口类的参数。

Lambda 表达式引发的 8 种写法

第 1 种写法

这是原始代码,它的本质是用 object 关键字定义了一个匿名内部类:

1
2
3
4
5
image.setOnClickListener(object: View.OnClickListener {
override fun onClick(v: View?) {
gotoPreview(v)
}
})

第 2 种写法

在这种情况下,object 关键字可以被省略。这时候它在语法层面就不再是匿名内部类了,它更像是 Lambda 表达式了,因此它里面 override 的方法也要跟着删掉:

1
2
3
image.setOnClickListener(View.OnClickListener { v: View? ->
gotoPreview(v)
})

上面的View.OnClickListener被称为 SAM Constructor(SAM 构造器),它是编译器为我们生成的。

第 3 种写法

由于 Kotlin 的 Lambda 表达式是不需要 SAM Constructor 的,所以它也可以被删掉:

1
2
3
image.setOnClickListener({ v: View? ->
gotoPreview(v)
})

第 4 种写法

由于 Kotlin 支持类型推导,所以 View 可以被删掉:

1
2
3
image.setOnClickListener({ v ->
gotoPreview(v)
})

第 5 种写法

当 Kotlin Lambda 表达式只有一个参数的时候,它可以被写成 it:

1
2
3
image.setOnClickListener({ it ->
gotoPreview(it)
})

第 6 种写法

Kotlin Lambda 的 it 是可以被省略的:

1
2
3
image.setOnClickListener({
gotoPreview(it)
})

第 7 种写法

当 Kotlin Lambda 作为函数的最后一个参数时,Lambda 可以被挪到外面:

1
2
3
image.setOnClickListener() {
gotoPreview(it)
}

第 8 种写法

当 Kotlin 只有一个 Lambda 作为函数参数时,() 可以被省略:

1
2
3
image.setOnClickListener {
gotoPreview(it)
}

动画显示演变过程:

函数类型、高阶函数以及 Lambda 表达式三者之间的关系:

难点:带接收者的函数类型


以apply函数为例
不用 apply:

1
2
3
4
5
6
if (user != null) {
...
username.text = user.name
website.text = user.blog
image.setOnClickListener { gotoImagePreviewActivity(user) }
}

使用 apply:

1
2
3
4
5
6
user?.apply {
...
username.text = name
website.text = blog
image.setOnClickListener { gotoImagePreviewActivity(this) }
}

反推apply函数:

1
2
3
4
5
6
7
8
9
10
11
// apply 肯定是个函数,所以有 (),只是被省略了
user?.apply() {
...
}

// Lambda 肯定是在 () 里面
user?.apply({ ... })

// 由于 gotoImagePreviewActivity(this) 里的 this 代表了 user
// 所以 user 应该是 apply 函数的一个参数,而且参数名为:this
user?.apply({ this: User -> ... })

apply 其实是接收了一个 Lambda 表达式:{ this: User -> … }。那么现在,我们就尝试来实现这个 apply 方法:

1
2
3
4
5
6
7
8
9
10
fun User.apply(self: User, block: (self: User) -> Unit): User{
block(self)
return this
}

user?.apply(self = user) { self: User ->
username.text = self.name
website.text = self.blog
image.setOnClickListener { gotoImagePreviewActivity(this) }
}

由于 Kotlin 里面的函数形参是不允许被命名为 this 的,因此我这里用的是 self。另外,这里我们自己写出来的 apply,仍然还要通过 self.name 这样的方式来访问成员变量,但 Kotlin 的语言设计者能做到这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//           改为this             改为this
// ↓ ↓
fun User.apply(this: User, block: (this: User) -> Unit): User{
// 这里还要传参数
// ↓
block(this)
return this
}

user?.apply(this = user) { this: User ->
...
// this 可以省略
// ↓
username.text = this.name
website.text = blog
image.setOnClickListener { gotoImagePreviewActivity(this) }
}

从上面的例子能看到,我们反推的 apply 实现会比较繁琐:需要我们传入 this:user?.apply(this = user)。需要我们自己调用:block(this)。因此,Kotlin 就引入了带接收者的函数类型,可以简化 apply 的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//              带接收者的函数类型
// ↓
fun User.apply(block: User.() -> Unit): User{
// 不用再传this
// ↓
block()
return this
}

user?.apply { this: User ->
// this 可以省略
// ↓
username.text = this.name
website.text = this.blog
image.setOnClickListener { gotoImagePreviewActivity(this) }
}

上面的 apply 方法是不是看起来就像是在 User 里,增加了一个成员方法 apply()?

1
2
3
4
5
6
7
8
9
10
11
class User() {
val name: String = ""
val blog: String = ""

fun apply() {
// 成员方法可以通过 this 访问成员变量
username.text = this.name
website.text = this.blog
image.setOnClickListener { gotoImagePreviewActivity(this) }
}
}

所以,从外表上看,带接收者的函数类型,就等价于成员方法。但从本质上讲,它仍是通过编译器注入 this 来实现的。

那么,带接收者的函数类型,可看成扩展函数,从语法层面讲,扩展函数就相当于成员函数。

关注我