Kotlin学习与实践 (九)带接收者的lambda及Java的函数式接口
带接收者的lambda
* 在lambda 函数体内可以调用一个不同对象的方法,而且无须借助任何额外限定符;这种能力再Java中是找不到的。
* 这样的lambda叫做“带接收者的lambda”
先举个普通函数作为反例:
fun alphabet(): String { val result = StringBuilder() for (letter in 'A'..'Z') { result.append(letter) } result.append("\nNow ,I know the alphabet") return result.toString() }
在上面的例子中可以看到函数中对 result 对象反复的调用,如果反复调用的多了就会变得很糟,Kotlin 带接受者的lambda就解决了这个问题。
首先看wtih函数:
* with 语法 是一个接收两个参数的函数:严格的写应该是 with(aa,{...lambda...}), 利用把lambda 作为最后一个参数可以放到括号外面的约定可以提高可读性 with(xx){...lambda...}
* with函数把第一个参数 转化成 作为第二个参数的lambda 的接收者,可以显式地通过this 引用来访问这个接收者,也可以省略this 引用直接访问
fun alphabetWith(): String { val result = StringBuilder() return with(result) { //指定接收者的值,然后就可以在lambda中使用 for (letter in 'A'..'Z') { this.append(letter) //通过this显示的来调用接收者 } append("\nNow ,I know the alphabet")//也可以省掉this 来调用接收者 result.append("hahaha") this.toString() //从lambda中返回 } }
* 可以使用表达式函数体语法继续简化函数。
fun alpabetWithF() = with(StringBuilder()) { for (letter in 'A'..'Z') { this.append(letter) //通过this显示的来调用接收者 } append("\nNow ,I know the alphabet")//也可以省掉this 来调用接收者 append("hahaha") toString() //从lambda中返回 }
* with返回值是执行lambda代码的结果,改结果就是lambda中的最后一个表达式的值。
* 如果你想返回的是接收者对象(传入lambda的对象)而不是lambda执行的结果时候,apply函数就排上用场了。
* apply被声明成一个扩展函数。它的接收者变成了作为实参的lambda的接收者。
* 执行apply的结果是StringBuilder,所以接下来你可以调用toString把它转化成String。
fun alpabetApply() = StringBuilder().apply { for (letter in 'A'..'Z') { this.append(letter) //通过this显示的来调用接收者 } append("\nNow ,I know the alphabet")//也可以省掉this 来调用接收者 append("hahaha") }.toString()
* Kotlin中可以再任意对象上使用apply,不需要任何特别的支持
* apply 允许你使用紧凑的表达式函数体的风格
* lambda执行之后,apply返回已经初始化过的接收者实例
fun createViewWithCustomAttribites(context:Context)= TextView(context).apply{ text = "Simple Text" testSize = 20 setPadding(20,15,1,0) }
以上是Kotlin中最典型最基本的带接受者的lambda函数,除了with apply之外还有其他的使用起来很赞的带接受者的函数....
使用Java的函数式接口
* 在Kotlin中可以传递一个lambda 代替传统的Java中的匿名类做实参
* 使用lambda代替Java匿名类的方式可以工作的原因是 ActionListener 接口中只有一个抽象方法。(Runnable、Callable)
* 这种接口被称为函数式接口,或者SAM接口,SAM代表单抽象方法。
* Kotlin 允许你再调用接收函数式接口作为参数的方式时使用lambda,来保证代码的整洁。
fun lambdaInnerClass() { // val btn = Button() // btn.setOnclickListener{v-> println("")} val btn = Button() btn.addActionListener { e -> println("hahaha") } }
下面来一个演示的例子,首先放出Java定义的函数:
public class JavaCallTest { public void postponeComputation(int delay, Runnable computation) { Thread thread = new Thread(computation); try { thread.join(delay); thread.start(); } catch (InterruptedException e) { e.printStackTrace(); } } }
* 在Koltin中使用lambda代替匿名类参数,编译器会把最后打lambda 自动编译转换成一个Runnable实例传递给方法
val javaTest = JavaCallTest() fun testLambdaCallJava() { javaTest.postponeComputation(100) { println(42) } // 如下通过显示创建匿名对象也能达到效果 javaTest.postponeComputation(1000, object : Runnable { override fun run() { println("452") } }) }
* 这里有一点不一样,当你显式地声明对象时,每次调用都会创建一个新的对象。
* 使用lambda的情况不同:如果lambda没有访问任何来自定义它的函数的变量,响应的匿名类对象可以在多次调用之间重用。
val runnable = Runnable { println(42) } fun reUsing() { javaTest.postponeComputation(1000, runnable) }
* 上面的runnable 使用lambda对应生成的对象就会多次复用 因为没有没有引用函数中定义的变量
* 如果lambda从包围它的作用域中捕捉了变量,每次调用就不能再重复利用同一个实例了,这时每次就会创建一个新的对象,其中存储着被捕获的变量的值
* 如下:
fun handleComputation(id: String) { javaTest.postponeComputation(1000) { println(id) } }
SAM接口还有一些别的特性,暂时就不列举出来了...