Kotlin基础知识(九)——使用Java函数式接口

967 阅读3分钟
  • Java示例
public interface OnClickListener {
        void onClick(View v);
}
  • Kotlin示例
public interface OnClickListener { view -> ... }

这种方式可以工作的原因是OnClickListener接口只有一个抽象方法。这种接口被称为函数式接口,或者***SAM接口***,SAM代表单抽象方法,Java API 中随处可见像RunnableCallable这样的函数式接口,以及支持它们的方法。

一、把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引用指向的是包围它的类。