Kotlin的標準函數(或叫Scope functions)是指在Standard.kt中定義的函數,在任何Kotlin程式碼都可以自由呼叫他們:let、apply、with、also、run、takeIf和takeUnless等函數,適當使用這些函數可以幫助我們減少一些重複的程式碼。
基本上,這些函數的作用相同:在對象上執行一段程式碼。不同的是這個對象如何在作用域{}中變得可用,以及整個表達式的結果是什麼。
因為標準函數在本質上都非常相似,所以了解它們之間的差異很重要。每個標準函數之間有兩個主要區別:
引用上下文對象的方式。在標準函數的 lambda 內部,上下文對象可通過短引用而不是其實際名稱獲得。每個標準函數使用以下兩種方式之一來訪問對象:
返回值:
fun main(){
    val username = "ironman"
    // 不使用run,也許會這樣寫
    val isIllegalName = isTooLong(username)
    val result = check(isIllegalName)
    print(result) //結果: 註冊成功
    
    //使用run
    username.run(::isTooLong)
            .run(::check)
            .run(::println)
            //結果: 註冊成功
  
    //使用run巢狀寫法
    println(check(isTooLong(username)))  //結果: 註冊成功
    
}
//檢查長度
fun isTooLong(name : String) = name.length > 10
//判斷要印出的訊息
fun check(isTooLong : Boolean): String {
    return if (isTooLong) {
        "名字太長啦!"
    } else {
        "註冊成功"
    }
}  
前面文章曾配合安全呼叫來檢處理可空類型,這也是官方指南(在文章最後)建議的功能。這裡簡單講一下它的特性:
    //取numbers中元素的奇數平方值印出:
    val numbers = listOf<Int>(1, 2, 3, 4, 5)
    
    //使用的let的寫法:
    numbers.filter { it % 2 == 1 }.map { it * it }.let{println(it)} 
   
    //不使用的let的寫法:
    val resultList = numbers.filter { it % 2 == 1 }.map { it * it }
    println(resultList)
如果let內只有一個用it作為參數的函數時,也可以使用::引用方法,如: let{println(it)}可寫成let(::println)
//主建構函數預設0歲(這裡不討論胎兒)並居無定所
class Person(val name: String, var age: Int = 0, var neighborhood: String = "居無定所") {
    //搬到哪去
    fun moveTo(place: String) {
        neighborhood = place
        println("$age \b歲的$name \b搬到$neighborhood")
    }
    //長大了幾歲
    fun grownUp(increase: Int) {
        age += increase
    }
}
fun main(){
    //孟子沒人不知道吧,設定基本資料時,可以使用apply
    val mencius = Person("孟子", 3 , "墳地附近")
       
     //大家也都知道他媽媽出了名的愛搬家吧,年紀我亂掰的
     //年紀 、住處資料變更時可以使用apply 
    mencius.apply {
        println("$age \b歲的$name \b搬到$neighborhood")
        grownUp(1)
        moveTo("市集屠宰場附近")
        grownUp(1)
        moveTo("學堂旁")
    }    
    //結果:3歲的孟子搬到墳地附近
    //    4歲的孟子搬到市集屠宰場附近
    //    5歲的孟子搬到學堂旁
}
不使用apply的話,就要一直喊孟子出來:
   println("$age \b歲的$name \b住在$neighborhood")
   mencius.grownUp(1)
   mencius.moveTo("市集屠宰場附近")
   mencius.grownUp(1)
   mencius.moveTo("學堂旁")
   //執行結果同上。
    val numbers = listOf<Int>(1, 2, 3, 4, 5)
    with(numbers){
        println("numbers中有: $this")
        println("numbers中共有 $size 個元素")
    }
另一種用例是引入一個輔助對象,對象的屬性或函數可以用於計算出一個返回值
val numbers = listOf<Int>(1, 2, 3, 4, 5)
    val result = with(numbers) {
        //使用「+」運算子連接兩個句子
        "numbers中第一個元素 : ${first()}\n" +
        "numbers中最後一個元素 : ${last()}"
    }
    println(result)
    //結果:
    // numbers中第一個元素 : 1
    // numbers中最後一個元素 : 5
因為with使用方法比較不同,而且還要注意對象可空的問題,所以好像比較少用(?)
fun main(){
    val numbers = mutableListOf<Int>(1, 2, 3, 4, 5)    
    numbers.also { println(it) }.add(6)
    //結果: [1, 2, 3, 4, 5]
}
takeIf函數會判斷lambda中提供的條件運算式,若為true就會返回接收者物件,false返回null。
下例是一個隨機數字如果是大於40的偶數才會印出:
    val randomNum = (1..100).random()
    
    //由於takeIf有可能返回null值,所以使用安全呼叫
    randomNum.takeIf { it > 40 && it % 2 == 0 }?.let(::println)
takeIf的輔助性函數,兩者唯一區別是takeUnless是判斷給訂條件為false的時候才回返回原始接收者物件。如果是複雜的判斷建議還是使用takeIf比較好,語意上比較好讀不易混淆。
| Function | Object reference | Return value | Is extension function | 
|---|---|---|---|
| let | it | Lambda result | Yes | 
| run | this | Lambda result | Yes | 
| run | - | Lambda result | No: called without the context object | 
| with | this | Lambda result | No: called without the context object | 
| apply | this | Context object | Yes | 
| also | this | Context object | Yes | 
| takeIf | it | Context object/null | Yes | 
| takeUnless | it | Context object/null | Yes | 
註 : run函數有另一個不常用的版本是不需要接收者,不傳遞接收者引數,沒有作用域限制,返回lambda值。
- 在非空對像上執行 lambda:let
- 在局部範圍內將表達式作為變數引入:let
- 對象配置:apply
- 對象配置和計算結果:run
- 在需要表達式的地方運行語句:非擴展函數版的run
- 附加效果:also
- 對對象進行分組函數調用:with
不同功能的用例重疊,因此您可以根據項目或團隊中使用的特定約定來選擇功能。
儘管標準函數是一種使代碼更簡潔的方法,但請避免過度使用它們:它會降低代碼的可讀性並導致錯誤。避免嵌套標準函數並在鏈接它們時要小心:很容易對當前上下文對象和this或it的值感到困惑。
參考
kotlin權威2.0
kotlin官方文檔
Khan Academy’s - Nice utility functions
明天見