Kotlin 的高阶函数、匿名函数和 Lambda
高阶函数
在说高阶函数之前我们先说一下如何在 Java 的方法中传递函数?Java 本身是不支持直接传递函数的,但是有一种间接的方式—接口,例如:
1 | OnClickListener listener = new OnClickListener() { |
其实这里只是想要一个 onClick
方法,但是限于语言的问题,这里不得不通过实现 OnClickListener
接口的方式来传递 onClick
方法。
Kotlin 支持传递函数类型,需要说明的是函数类型不是一种类型而是一类类型。对于函数类型,需要指明参数类型和返回值类型,例如:
1 | fun test(hello: (String) -> Unit) { |
通过对函数类型的了解,可以引入高阶函数的概念:
高阶函数:参数或者返回值为函数类型的函数,没有什么特别的
函数类型既然可以作为参数,当然也可以作为变量,但是需要使用 ::
,例如:
1 | fun hello(content: String) { |
那么这个 ::
到底是什么呢?官方说法,这个 ::
叫做函数引用 Function Reference。但是为什么不直接写函数名,而是要加上这个前缀呢?
因为加了 ::
,这个函数才成了对象。对于上面的内容可以反编译成字节码再编译成 Java 代码,就可以很清楚的知道 ::
的作用了。
1 | class Func { |
1 | public final class Func { |
Kotlin 里面 [函数可以作为参数] 的本质,是因为 Kotlin 中的函数可以作为对象存在。因为只有对象才可以作为参数传递,也只有对象才能赋值给变量。但是 Kotlin 中的函数本身的性质又决定了它没办法当做成一个对象。怎么办?Kotlin
的选择是创建一个和函数具有相同功能的对象,创建方式就是使用 ::
。
匿名函数
给函数类型变量赋值,除了使用 ::
拿现成的函数使用,还可以这么做:
1 | val test = fun hello(content: String) { |
另外,这种写法的话,函数的名字其实就没用了,所以你可以把它省掉:
1 | val test = fun(content: String) { |
这种写法叫做匿名函数。为什么叫匿名函数?很简单,因为没有名字啊。另外呢,刚才那种左边右边都有名字的写法,Kotlin 是不允许的。右边的函数既然要名字也没有用,Kotlin 干脆就不许它有名字了。所以开篇的 Java 代码在 Kotlin 中可以这么写:
1 | fun setOnClickListener(onClick: (View) -> Unit) { |
注意:匿名函数不是一个函数,它是一个函数类型的对象
Lambda
1 | view.setOnClickListener(fun(v: View): Unit) { |
如果 Lambda 是函数中的最后一个参数,那么可以把 Lambda 写在函数括号的外面,如下:
1 | view.setOnClickListener() { v: View -> |
如果 Lambda 是函数中唯一的参数,还可以把函数括号去掉,如下:
1 | view.setOnClickListener { v: View -> |
如果 Lambda 是单参数的,还可以把 参数去掉,如下:
1 | view.setOnClickListener { |
注意:
- Lambda 和匿名函数很像,都是函数类型的对象。
- Lambda 的返回值是取函数最后一行的值,使用 return 会作为外层函数的返回值直接结束外层函数。
对比 Java 的 Lambda
Java 从 8 开始引入了对 Lambda 的支持,对于单抽象方法的接口——简称 SAM 接口,Single Abstract Method 接口——对于这类接口,Java 8 允许你用 Lambda 表达式来创建匿名类对象,但它本质上还是在创建一个匿名类对象,只是一种简化写法而已,所以 Java 的 Lambda 只靠代码自动补全就基本上能写了。而 Kotlin 里的 Lambda 和 Java 本质上就是不同的,因为 Kotlin 的 Lambda 是实实在在的函数类型的对象,功能更强,写法更多更灵活。
Kotlin 在 1.4 之前是不支持使用 Lambda 的方式来简写匿名类对象的,但是当和 Java 交互的时候,Kotlin 支持这种用法:当你的函数参数是 Java 的单抽象方法的接口的时候,你依然可以使用 Lambda 来写参数。但这其实也不是 Kotlin 增加了功能,而是对于来自 Java 的单抽象方法的接口,Kotlin 会为它们额外创建一个把参数替换为函数类型的桥接方法,让你可以间接地创建 Java 的匿名类对象。
这就是为什么,你会发现当你在 Kotlin 里调用 View.java 这个类的 setOnClickListener() 的时候,可以传 Lambda 给它来创建 OnClickListener 对象,但你照着同样的写法写一个 Kotlin 的接口,你却不能传 Lambda。
1 | interface Listener { |
但是在 Kotlin 1.4 之后添加了一个新的特性:函数式接口,具体可以看这里官网。使用 fun interface 后,上面的代码就可以正常运行:
1 | fun interface Listener { |