1. 程式人生 > >Kotlin學習與實踐 (九)帶接收者的lambda及Java的函數式接口

Kotlin學習與實踐 (九)帶接收者的lambda及Java的函數式接口

參數 通過 需要 語法 end handle brush 捕捉 tin

帶接收者的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接口還有一些別的特性,暫時就不列舉出來了...
 

Kotlin學習與實踐 (九)帶接收者的lambda及Java的函數式接口