- Java示例
public interface OnClickListener {
void onClick(View v);
}
- Kotlin示例
public interface OnClickListener { view -> ... }
这种方式可以工作的原因是OnClickListener接口只有一个抽象方法。这种接口被称为函数式接口,或者***SAM
接口***,SAM
代表单抽象方法,Java API 中随处可见像Runnable
和Callable
这样的函数式接口,以及支持它们的方法。
一、把lambda当作参数传递给Java方法
可以把lambda传给任何期望函数式接口的方法。
- 示例一、有一个Runnable类型的参数的方法:
void postponeComputation(int delay, Runnable computation)
在Kotlin中,可以调动用它并把一个lambda作为实参传给它。编译器会自动把它转换成一个Runnable的实例:
postponeComputation(1000) { println(42) }
注意,“一个
Runnable
的实例”,指的是“一个实现了Runnable
接口的匿名类的实例”。编译器会帮助你创建它,并使用lambda
作为单抽象方法——这个例子中是run
方法——的方法体。
通过显式地创建一个实现了Runnable的匿名对象也能达到同样的效果:
// 把对象表达式作为函数式接口的实现传递
postpostComputation(1000, object: Runnable {
override fun run() {
println(42)
}
})
注意:
- 在显式地声明对象时,每次调用都会创建一个新的实例。
- 使用lambda的情况不同:如果lambda没有访问任何来自自定义它的函数的变量,相应的匿名类实例可以在多次调用之间重用:
// 整个程序只会创建一个Runnable的实例 postponeComputation(1000) { println(42) }
因此,完全等价的实现应该是下面这段代码中的显式object声明,它把Runnable实例存储在一个变量中,并且每次调用的时候都使用这个变量:
// 编译成全局变量;程序中仅此一个实例
val runnable = Runnable { println(42) }
fun handleComputation() {
// 每次postponeComputation调用时用的是一个对象
postponeComputation(1000, runnable)
}
如果lambda从包围它的作用域中捕获了变量,每次调用就不再可能重用同一个实例了。这种情况下,每次调用时编译器都会创建一个新对象,其中存储着呗捕获的变量的值。
// lambda会捕获“id”这个变量
fun handleComputation(id: String) {
// 每次handleComputation调用时都创建一个Runnable的新实例
postponeComputation(1000) { println(id) }
}
注意:这里讨论的为lambda创建一个匿名类,以及该类的实例的方法只对期望函数式接口的Java方法有效,但是对集合使用Kotlin扩展方法的方式并不适用。如果把lambda传给了标记成***
inline
的Kotlin函数,是不会创建任何匿名类的。而大多数的库函数都标成了inline
***。
二、SAM构造方法:显式地把lambda转换成函数式接口
SAM构造方法是编译器生成的函数,让你执行从lambda到函数式接口实例的显式转换。可以在编译器不会自动应用转换的上下文中使用它。
例如,如果有一个方法返回的是一个函数式接口的实例,不能直接返回一个lambda,要用SAM构造方法把它保证起来。
- 使用SAM构造方法来返回值
// 定义
fun createAllDoneRunnable(): Runnable {
return Runnable { println("All done!") }
}
// 测试
>>> createAllDoneRunnable().run()
All done!
SAM构造方法的名称和底层函数式接口的名称一样。SAM构造方法只接受一个餐宿——一个被用作函数式接口单抽象方法体的lambda——并返回实现了这个接口的类的一个实例。
Lambda和添加 / 移除监听器
注意lambda内部没有匿名类对象那样的this:没有办法引用在lambda转换成的匿名类的实例。从编译器的角度来看,lambda是一个代码块,不是一个对象,而且也不能把它当成对象引用。Lambda中的this引用指向的是包围它的类。