Kotlin学习与实践 (九)带接收者的lambda及Java的函数式接口

带接收者的lambda

* 在lambda 函数体内可以调用一个不同对象的方法,而且无须借助任何额外限定符;这种能力再Java中是找不到的。
* 这样的lambda叫做“带接收者的lambda”

先举个普通函数作为反例:
fun alphabet(): String {
    val result = StringBuilder()
    for (letter in 'A'..'Z') {
        result.append(letter)
    }
    result.append("
Now ,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("
Now ,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("
Now ,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("
Now ,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接口还有一些别的特性,暂时就不列举出来了...
 
原文地址:https://www.cnblogs.com/mauiie/p/SAM_lambda.html