kotlin lambda表示式

bobby_developer發表於2021-08-03

1. 函式

1.1 函式宣告

Kotlin 中的函式使用 fun 關鍵字宣告:

fun double(x: Int): Int {
    return 2 * x
}
複製程式碼

1.2 函式用法

呼叫函式使用傳統的方法:

val result = double(2)
複製程式碼

呼叫成員函式使用點表示法:

Stream().read() // 建立類 Stream 例項並呼叫 read()
複製程式碼

1.3引數

函式引數使用 Pascal 表示法定義,即 name: type。引數用逗號隔開。 每個引數必須有顯式型別:

fun powerOf(number: Int, exponent: Int): Int { /*……*/ }
複製程式碼

You can use a trailing comma when you declare function parameters:

fun powerOf(
    number: Int,
    exponent: Int, // trailing comma
) { /*...*/ }
複製程式碼

1.4 返回 Unit 的函式

如果一個函式不返回任何有用的值,它的返回型別是 UnitUnit 是一種只有一個值——Unit 的型別。這個值不需要顯式返回:

fun printHello(name: String?): Unit {
    if (name != null)
        println("Hello $name")
    else
        println("Hi there!")
    // `return Unit` 或者 `return` 是可選的
}
複製程式碼

Unit 返回型別宣告也是可選的。上面的程式碼等同於:

fun printHello(name: String?) { …… }
複製程式碼

1.5 單表示式函式

當函式返回單個表示式時,可以省略花括號並且在 = 符號之後指定程式碼體即可:

fun double(x: Int): Int = x * 2
複製程式碼

當返回值型別可由編譯器推斷時,顯式宣告返回型別是可選的:

fun double(x: Int) = x * 2
複製程式碼

2. 高階函式與 lambda 表示式

​ Kotlin 函式都是頭等的,這意味著它們可以儲存在變數與資料結構中、作為引數傳遞給其他高階函式以及從其他高階函式返回。可以像操作任何其他非函式值一樣操作函式。

2.1 高階函式

​ 高階函式是將函式用作引數或返回值的函式。一個不錯的示例是集合的函式式風格的 fold, 它接受一個初始累積值與一個接合函式,並通過將當前累積值與每個集合元素連續接合起來代入累積值來構建返回值:

fun <T, R> Collection<T>.fold(
    initial: R, 
    combine: (acc: R, nextElement: T) -> R
): R {
    var accumulator: R = initial
    for (element: T in this) {
        accumulator = combine(accumulator, element)
    }
    return accumulator
}
複製程式碼

​ 在上述程式碼中,引數 combine 具有函式型別 (R, T) -> R,因此 fold 接受一個函式作為引數, 該函式接受型別分別為 RT 的兩個引數並返回一個 R 型別的值。 在 for-迴圈內部呼叫該函式,然後將其返回值賦值給 accumulator。為了呼叫 fold,需要傳給它一個函式型別的例項作為引數,而在高階函式呼叫處,(下文詳述的)lambda 表達 式廣泛用於此目的。

​ 為了呼叫 fold,需要傳給它一個函式型別的例項作為引數,而在高階函式呼叫處,(下文詳述的)lambda 表達 式廣泛用於此目的。

val items = listOf(1, 2, 3, 4, 5)

// Lambdas 表示式是花括號括起來的程式碼塊。
items.fold(0, { 
    // 如果一個 lambda 表示式有引數,前面是引數,後跟“->”
    acc: Int, i: Int -> 
    print("acc = $acc, i = $i, ") 
    val result = acc + i
    println("result = $result")
    // lambda 表示式中的最後一個表示式是返回值:
    result
})

// lambda 表示式的引數型別是可選的,如果能夠推斷出來的話:
val joinedToString = items.fold("Elements:", { acc, i -> acc + " " + i })

// 函式引用也可以用於高階函式呼叫:
val product = items.fold(1, Int::times)

複製程式碼

2.2 函式型別

Kotlin 使用類似 (Int) -> String 的一系列函式型別來處理函式的宣告: val onClick: () -> Unit = ……

這些型別具有與函式簽名相對應的特殊表示法,即它們的引數和返回值:

  • 所有函式型別都有一個圓括號括起來的引數型別列表以及一個返回型別:(A, B) -> C 表示接受型別分別為 AB 兩個引數並返回一個 C 型別值的函式型別。 引數型別列表可以為空,如 () -> AUnit 返回型別不可省略。
  • 函式型別可以有一個額外的接收者型別,它在表示法中的點之前指定: 型別 A.(B) -> C 表示可以在 A 的接收者物件上以一個 B 型別引數來呼叫並返回一個 C 型別值的函式。 帶有接收者的函式字面值通常與這些型別一起使用。
  • 掛起函式屬於特殊種類的函式型別,它的表示法中有一個 suspend 修飾符 ,例如 suspend () -> Unit 或者 suspend A.(B) -> C

函式型別表示法可以選擇性地包含函式的引數名:(x: Int, y: Int) -> Point。 這些名稱可用於表明引數的含義。

如需將函式型別指定為可空,請使用圓括號:((Int, Int) -> Int)?

函式型別可以使用圓括號進行接合:(Int) -> ((Int) -> Unit)

箭頭表示法是右結合的,(Int) -> (Int) -> Unit 與前述示例等價,但不等於 ((Int) -> (Int)) -> Unit

還可以通過使用型別別名給函式型別起一個別稱:

typealias ClickHandler = (Button, ClickEvent) -> Unit
複製程式碼

2.3 函式型別例項化

有幾種方法可以獲得函式型別的例項:

  • 使用函式字面值的程式碼塊,採用以下形式之一:

    帶有接收者的函式字面值可用作帶有接收者的函式型別的值。

  • 使用已有宣告的可呼叫引用

    • 頂層、區域性、成員、擴充套件函式::isOddString::toInt
    • 頂層、成員、擴充套件屬性List<Int>::size
    • 建構函式::Regex
  • 使用實現函式型別介面的自定義類的例項:

    class IntTransformer: (Int) -> Int {
        override operator fun invoke(x: Int): Int = TODO()
    }
    
    val intFunction: (Int) -> Int = IntTransformer()
    複製程式碼

    如果有足夠資訊,編譯器可以推斷變數的函式型別:

    val a = { i: Int -> i + 1 } // 推斷出的型別是 (Int) -> Int

  • 帶與不帶接收者的函式型別非字面值可以互換,其中接收者可以替代第一個引數,反之亦然。例如,(A, B) -> C 型別的值可以傳給或賦值給期待 A.(B) -> C 的地方,反之亦然:

帶與不帶接收者的函式型別非字面值可以互換,其中接收者可以替代第一個引數,反之亦然。例如,(A, B) -> C 型別的值可以傳給或賦值給期待 A.(B) -> C 的地方,反之亦然:

val repeatFun: String.(Int) -> String = { times -> this.repeat(times) }
val twoParameters: (String, Int) -> String = repeatFun // OK

fun runTransformation(f: (String, Int) -> String): String {
    return f("hello", 3)
}
val result = runTransformation(repeatFun) // OK

複製程式碼

請注意,預設情況下推斷出的是沒有接收者的函式型別,即使變數是通過擴充套件函式引用來初始化的。 如需改變這點,請顯式指定變數型別。

2.4 Lambda 表示式與匿名函式

Lambda表示式的本質其實是匿名函式,因為在其底層實現中還是通過匿名函式來實現的。但是我們在用的時候不必關心起底層實現。不過Lambda的出現確實是減少了程式碼量的編寫,同時也是程式碼變得更加簡潔明瞭。Lambda作為函數語言程式設計的基礎,其語法也是相當簡單的。這裡先通過一段簡單的程式碼演示沒讓大家瞭解Lambda表示式的簡潔之處。

​ 例:

// 這裡舉例一個Android中最常見的按鈕點選事件的例子
mBtn.setOnClickListener(object : View.OnClickListener{
        override fun onClick(v: View?) {
            Toast.makeText(this,"onClick",Toast.LENGTH_SHORT).show()
        }
    })
複製程式碼

等價於

// 呼叫
mBtn.setOnClickListener { Toast.makeText(this,"onClick",Toast.LENGTH_SHORT).show() }
複製程式碼

2.4.1 Lambda 表示式與匿名函式

lambda 表示式與匿名函式是“函式字面值”,即未宣告的函式, 但立即做為表示式傳遞。考慮下面的例子:

max(strings, { a, b -> a.length < b.length })
複製程式碼

函式 max 是一個高階函式,它接受一個函式作為第二個引數。 其第二個引數是一個表示式,它本身是一個函式,即函式字面值,它等價於以下具名函式:

fun compare(a: String, b: String): Boolean = a.length < b.length
複製程式碼

相關文章