Kotlin 高階函數與 Lambda 表達式

在 Kotlin 中函數也是一等公民,這意味著我們定義的變量、函數參數、返回值都可以是函數類型的,可以像操作其它非函數值一樣操作函數,確實也方便了不少。對 Android 開發(fā)者而言這無疑是一個較大的變化(雖然從 Java8 開始也有了類似的操作),同時也是 Kotlin 中相對重要的知識點,值得我們深入學習。

一、高階函數

高階函數是將函數作為參數或返回值的函數。這里有三個問題需要我們先思考:

  1. 如何定義一個函數的參數為函數類型?
  2. 如何使用這個函數類型的參數?
  3. 函數作為參數如何傳遞?

先看一個高階函數實現的例子:

class Calculator {
    fun sum(a: Int, b: Int): Int = a + b

    fun calculate(a: Int, b: Int, cal: (Int, Int) -> Int) {
        print("a + b = ${cal(a, b)}")
    }
}

fun main(args: Array<String>) {
    val calculator = Calculator()
    calculator.calculate(1, 1, calculator::sum)
}
// 輸出
a + b = 2

現在回答上邊的三個問題

  1. cal: (Int, Int) -> Int就定義了calculate()函數的第三個參數為函數類型,同時該類型的函數有兩個Int類型參數,返回值為Int類型。所以->左邊括號部分為函數參數類型聲明,右邊為函數的返回值。注意,如果是無參函數括號()不能省略。
  2. cal(a, b)就是使用這個函數類型的參數,和普通函數的調用沒啥區(qū)別。
  3. 注意main()方法的最后一行,我們使用了calculator::sum這種寫法,注意sum()方法的聲明和cal參數的類型是一致的。

同樣的原理,函數也可以作為返回值,簡單修改上邊的代碼:

class Calculator {
    fun sum(a: Int, b: Int): Int = a + b
    fun getSum(): (Int, Int) -> Int = this::sum
}

fun main(args: Array<String>) {
    val f = Calculator().getSum()
    print(f(1, 1))
}
// 輸出
2

二、Lambda 表達式

1、初識 Lambda 表達式

Lambda 表達式本質上是可以傳遞給其它函數來作為參數的代碼塊。咦?這樣說 Lambda 表達式也可作為高階函數的函數類型參數的值?當然,Lambda 表達式為函數式編程提供了更好的實現。先修改上邊的代碼:

class Calculator {
    fun calculate(a: Int, b: Int, cal: (Int, Int) -> Int) {
        print("a + b = ${cal(a, b)}")
    }
}

fun main(args: Array<String>) {
    val calculator = Calculator()
    calculator.calculate(1, 1, { a, b -> a + b })
}
// 輸出
a + b = 2

和第一部分中主要的不同之處是,調用calculate()方法時使用了{ a, b -> a + b }作為第三個參數的值,其實{ a, b -> a + b }就是一個 Lambda 表達式,這里省略了它的參數類型,完整的如下:

{ a: Int, b: Int -> a + b }

通過例子我們可以看出 Lambda 表達式的書寫規(guī)則如下:

  1. Lambda 表達式總是被花括號{}包裹著
  2. ->左邊為參數定義部分,多個參數用逗號,間隔,類似函數的參數聲明,但 Lambda 表達式的參數類型可以省略(編譯器可以推斷類型)。
  3. ->右邊為 Lambda 表達式要執(zhí)行的業(yè)務邏輯,類似于函數體

2、Lambda 表達式的一些特性

  1. 如果函數的最后一個參數接受函數,那么傳入的 Lambda 表達式可以放在圓括號之外:
fun calculate(a: Int, b: Int, cal: (Int, Int) -> Int) {
    print("a + b = ${cal(a, b)}")
}

fun main(args: Array<String>) {
    calculate(1, 1) { a, b -> a + b }
}
  1. 如果 Lambda 表達式只有一個參數,并且編譯器自己可以識別出簽名,也可以不用聲明唯一的參數并忽略->, 該參數會隱式聲明為it
fun calculate(a: Int, cal: (Int) -> Int) {
    print(cal(a))
}

fun main(args: Array<String>) {
    calculate(2) { it * it }
}
// 輸入
4
  1. 如果 Lambda 表達式沒有參數,也可以忽略->, 并且不會隱式聲明參數it,可參考下一點的例子。

  2. 如果 Lambda 表達式是調用時唯一的參數,那么圓括號也可以省略:

fun myPrint(p: () -> Unit) {
    p()
}

fun main(args: Array<String>) {
    myPrint {
        print("timestamp is ${System.currentTimeMillis()}")
    }
}
  1. Lambda 表達式將隱式返回最后一個表達式的值,但可以使用限定的返回語法,即通過標簽顯式返回一個值:
fun calculate(a: Int, cal: (Int) -> Int) {
    print(cal(a))
}

fun main(args: Array<String>) {
    calculate(2) {
        it * it
    }

    calculate(2) {
        return@calculate it * it
    }
}
  1. 從 Kotlin1.1 起,如果 Lambda 表達式的參數未使用,則可以用下劃線代替其名稱:
fun calculate(a: Int, b: Int, cal: (Int, Int) -> Int) {
    print(cal(a, b))
}

fun main(args: Array<String>) {
    calculate(1, 2) { _, b ->
        println("a 參數未使用")
        b * b
    }
}
  1. 從 Kotlin1.1 起,Lambda 表達式參數支持解構聲明語法,我們通過 Kotlin 內置的forEach()方法遍歷map來測試:
fun main(args: Array<String>) {
    val map = mapOf(1 to 1, 2 to 2, 3 to 3)

    map.forEach {
        println("${it.key} to ${it.value}")
    }

    // 顯示聲明it參數的類型
    map.forEach { entry: Map.Entry<Int, Int> ->
        println("${entry.key} to ${entry.value}")
    }
    // 將Map.Entry類型的it參數解構
    map.forEach { (k, v) ->
        println("$k to $v")
    }
}

三、匿名函數

其實 Lambda 表達式有一個問題,就是無法顯示的指定其返回值的類型,雖然可以自動推斷出返回值類型,如果確實需要顯式指定返回值類型,可以匿名函數,和普通的函數類似,只是省略了函數名:

fun(a: Int, b: Int): Int {
    return a + b
}
// 或者
fun(a: Int, b: Int): Int = a + b

用法和 Lambda 表達式也有差別的:

fun calculate(a: Int, b: Int, cal: (Int, Int) -> Int) {
    println("a + b = ${cal(a, b)}")
}

val sum = fun(a: Int, b: Int): Int = a + b

fun main(args: Array<String>) {
    calculate(1, 1, sum)
    calculate(2, 2, fun(a: Int, b: Int): Int = a + b)
}

四、訪問閉包

Lambda 表達式或者匿名函數(以及局部函數對象表達式) 可以訪問其 閉包 ,即在外部作用域中聲明的變量。 與 Java 不同的是可以修改閉包中捕獲的變量:

fun calculate(a: Int, b: Int, cal: (Int, Int) -> String) {
    print(cal(a, b))
}

fun main(args: Array<String>) {
    var str: String
    calculate(1, 1) { a, b ->
        str = "a + b = "
        "$str${a + b}"
    }
}
// 輸出
a + b = 2
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容