高阶函数分两个部分实现
1、用函数类型替代接口定义;
2、用 Lambda 表达式作为函数参数。
举例,view的点击事件
// View.java
// 成员变量
private OnClickListener mOnClickListener;
private OnContextClickListener mOnContextClickListener;
// 监听手指点击事件
public void setOnClickListener(OnClickListener l) {
mOnClickListener = l;
}
// 为传递这个点击事件,专门定义了一个接口
public interface OnClickListener {
void onClick(View v);
}
// 监听鼠标点击事件
public void setOnContextClickListener(OnContextClickListener l) {
getListenerInfo().mOnContextClickListener = l;
}
// 为传递这个鼠标点击事件,专门定义了一个接口
public interface OnContextClickListener {
boolean onContextClick(View v);
}
//View.kt
// (View) -> Unit 就是「函数类型 」
// ↑ ↑
var mOnClickListener: ((View) -> Unit)? = null
var mOnContextClickListener: ((View) -> Unit)? = null
// 高阶函数
fun setOnClickListener(l: (View) -> Unit) {
mOnClickListener = l;
}
// 高阶函数
fun setOnContextClickListener(l: (View) -> Unit) {
mOnContextClickListener = l;
}
View.java & View.kt实现了等价的功能; 函数类型替代了原来的接口定义
调用方的差距
java
// 设置手指点击事件
image.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
gotoPreview();
}
});
// 设置鼠标点击事件
image.setOnContextClickListener(new View.OnContextClickListener() {
@Override
public void onContextClick(View v) {
gotoPreview();
}
});
kt
// { gotoPreview() } 就是 Lambda
// ↑
image.setOnClickListener({ gotoPreview() })
image.setOnContextClickListener({ gotoPreview() })
Kotlin 中引入高阶函数会带来几个好处:一个是针对定义方,代码中减少了两个接口类的定义;另一个是对于调用方来说,代码也会更加简洁。
函数类型:
字面意思,函数类型(Function Type)就是函数的类型。
// (Int, Int) ->Float 这就是 add 函数的类型
// ↑ ↑ ↑
fun add(a: Int, b: Int): Float { return (a+b).toFloat() }
注释里面的“↑”代表的是一种映射关系。其实,将第三行代码里的“Int Int Float”抽出来,就代表了该函数的类型。
将函数的“参数类型”和“返回值类型”抽象出来后,就得到了“函数类型”。(Int, Int) ->Float 就代表了参数类型是两个 Int,返回值类型为 Float 的函数类型。
函数的引用:
普通的变量也有引用的概念,我们可以将一个变量赋值给另一个变量。而这一点,在函数上也是同样适用的,函数也有引用,并且也可以赋值给变量。
// 函数赋值给变量 函数引用
// ↑ ↑
val function: (Int, Int) -> Float = ::add
高阶函数:
标准定义:高阶函数是将函数用作参数或返回值的函数。
换句话说,一个函数的参数或是返回值,它们当中有一个是函数的情况下,这个函数就是高阶函数。
// 函数作为参数的高阶函数
// ↓
fun setOnClickListener(l: (View) -> Unit) { ... }
Lambda
Kotlin 语言的设计者是用 Lambda 表达式作为函数参数的,那么这里的 Lambda,就可以理解为是函数的简写:
SAM 转换
SAM 是 Single Abstract Method 的缩写,意思就是只有一个抽象方法的类或者接口。但在 Kotlin 和 Java 8 里,SAM 代表着只有一个抽象方法的接口。只要是符合 SAM 要求的接口,编译器就能进行 SAM 转换,也就是我们可以使用 Lambda 表达式,来简写接口类的参数。
注意,Java 8 中的 SAM 有明确的名称,叫做函数式接口(FunctionalInterface)。FunctionalInterface 的限制如下,缺一不可:
必须是接口,抽象类不行;
该接口有且仅有一个抽象的方法,抽象方法个数必须是 1,默认实现的方法可以有多个。
也就是说,对于 View.java 来说,它虽然是 Java 代码,但 Kotlin 编译器知道它的参数 符合 SAM 转换的条件,所以会自动做转换。
Lambda 表达式引发的 8 种写法
当一个函数的参数是 SAM 的情况下,我们同样也可以使用 Lambda 作为参数。
所以,我们既可以用匿名内部类的方式传参,也可以使用 Lambda 的方式传参。
在以上两种写法的中间,还有 6 种“过渡状态”的写法。同样的代码,能有 8 种不同的写法。
第 1 种写法
这是原始代码,它的本质是用 object 关键字定义了一个匿名内部类:
image.setOnClickListener(object: View.OnClickListener {
override fun onClick(v: View?) {
gotoPreview(v)
}
})
第 2 种写法
在这种情况下,object 关键字可以被省略。这时候它在语法层面就不再是匿名内部类了,它更像是 Lambda 表达式了,因此它里面 override 的方法也要跟着删掉:
image.setOnClickListener(View.OnClickListener { v: View? ->
gotoPreview(v)
})
第 3 种写法
由于 Kotlin 的 Lambda 表达式是不需要 SAM Constructor 的,所以它也可以被删掉:
image.setOnClickListener({ v: View? ->
gotoPreview(v)
})
第 4 种写法
由于 Kotlin 支持类型推导,所以 View 可以被删掉:
image.setOnClickListener({ v ->
gotoPreview(v)
})
第 5 种写法
当 Kotlin Lambda 表达式只有一个参数的时候,它可以被写成 it:
image.setOnClickListener({ it ->
gotoPreview(it)
})
第 6 种写法
Kotlin Lambda 的 it 是可以被省略的:
image.setOnClickListener({
gotoPreview(it)
})
第 7 种写法
当 Kotlin Lambda 作为函数的最后一个参数时,Lambda 可以被挪到外面:
image.setOnClickListener() {
gotoPreview(it)
}
第 8 种写法
当 Kotlin 只有一个 Lambda 作为函数参数时,() 可以被省略:
image.setOnClickListener {
gotoPreview(it)
}
小结:
将函数的参数类型和返回值类型抽象出来后,我们就得到了函数类型。比如(View) -> Unit 就代表了参数类型是 View,返回值类型为 Unit 的函数类型。
如果一个函数的“参数”或者“返回值”的类型是函数类型,那这个函数就是高阶函数。很明显,我们刚刚就写了一个高阶函数,只是它比较简单而已。
Lambda 就是函数的一种简写。
回顾下函数类型、高阶函数以及 Lambda 表达式三者之间的关系:
难点:带接收者的函数类型
举例,定一个apply()方法,带接收者的函数类型
// 带接收者的函数类型
// ↓
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 的使用场景
不用 apply:
if (user != null) {
...
username.text = user.name
website.text = user.blog
image.setOnClickListener { gotoImagePreviewActivity(user) }
}
使用 apply:
user?.apply {
...
username.text = name
website.text = blog
image.setOnClickListener { gotoImagePreviewActivity(this) }
}
如果不引入带接收者的函数类型,长这样:
// 改为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 的定义:
// 带接收者的函数类型
// ↓
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) }
}
带接收者的函数类型,就等价于成员方法。
class User() {
val name: String = ""
val blog: String = ""
fun apply() {
// 成员方法可以通过 this 访问成员变量
username.text = this.name
website.text = this.blog
image.setOnClickListener { gotoImagePreviewActivity(this) }
}
}
但从本质上讲,它仍是通过编译器注入 this 来实现的。
图解如下:
带接收者的函数类型,也能代表扩展函数呢。从语法层面讲,扩展函数就相当于成员函数。
改进 单例的抽象类模板
改进前BaseSingleton 的代码:
abstract class BaseSingleton<in P, out T> {
@Volatile
private var instance: T? = null
// ①
// ↓
protected abstract fun creator(param: P): T
fun getInstance(param: P): T =
instance ?: synchronized(this) {
instance ?: creator(param).also { instance = it }
}
}
class PersonManager private constructor(name: String) {
companion object : BaseSingleton<String, PersonManager>() {
override fun creator(param: String): PersonManager = PersonManager(param)
}
}
BaseSingleton 是单例抽象模板,而 PersonManager 则是实际的单例类。
改进点:抽象方法creator;creator应该定义成属性。
途径:通过 高阶函数 将一个方法改成属性
将 creator 改成一个类型为:(P)->T的属性
改进后BaseSingleton 的代码:
abstract class BaseSingleton<in P, out T> {
@Volatile
private var instance: T? = null
// 变化在这里,函数类型的属性
// ↓ ↓
protected abstract val creator: (P)-> T
fun getInstance(param: P): T =
instance ?: synchronized(this) {
instance ?: creator(param).also { instance = it }
}
}
将 creator 改成了一个抽象的属性,如果其他的单例类继承了 BaseSingleton 这个类,就必须实现这个 creator 属性。不过,PersonManager 该怎么写呢?
通过 函数引用 正确实现 creator 这个函数类型的属性
class PersonManager private constructor(name: String) {
companion object : BaseSingleton<String, PersonManager>() {
// 函数引用
// ↓
override val creator = ::PersonManager
}
}
直接将 PersonManager 的构造函数,以函数引用的方式传给了 creator 这个属性,这样就成功地实现了这个函数类型的属性。
前后对比,代码中的关键元素只是换了一种语法排列规则:从函数的语法变成了属性的语法,语法从复杂变得简洁,其中的关键元素并未丢失。两种代码是完全等价的,但后者更加简洁易懂