kotlin小记a-高阶函数与 lambda 表达式

1,698 阅读2分钟

高阶函数

高阶函数是将函数用作参数或返回值的函数。 例:

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 接受一个函数作为参数, 该函数接受类型分别为 R 与 T 的两个参数并返回一个 R 类型的值。 在 for-循环内部调用该函数,然后将其返回值赋值给 accumulator。

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

println(items.fold(0, { aac: Int, i: Int ->
    print("aac = $aac, i = $i, ")
    val result = aac + i
    println("result = $result")
    result
}))

/**
输出结果为:
aac = 0, i = 1, result = 1
aac = 1, i = 2, result = 3
aac = 3, i = 3, result = 6
aac = 6, i = 4, result = 10
aac = 10, i = 5, result = 15
15
*/

val joinedToString = items.fold("Elements:", { acc, i -> "$acc $i" })

println(joinedToString)
// 结果为 Elements: 1 2 3 4 5

val product = items.fold(1, Int::times)

println(product)
// 结果为 120

箭头表示法是右结合的

函数类型可以使用圆括号进行接合:(Int) -> ((Int) -> Unit)

箭头表示法是右结合的,(Int) -> (Int) -> Unit 与前述示例等价

可以通过使用类型别名给函数类型起一个别称:

typealias ClickHandler = (Button, ClickEvent) -> Unit

带与不带接收者的函数类型非字面值可以互换,其中接收者可以替代第一个参数,反之亦然。例如,(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

Lambda 表达式与匿名函数

lambda 表达式与匿名函数是“函数字面值”,即未声明的函数, 但立即做为表达式传递。

max(strings, { a, b -> a.length < b.length })

函数 max 是一个高阶函数,它接受一个函数作为第二个参数。 其第二个参数是一个表达式,它本身是一个函数,即函数字面值,它等价于以下具名函数:

fun compare(a: String, b: String): Boolean = a.length < b.length

Lambda 表达式语法

lambda 表达式总是括在花括号中, 完整语法形式的参数声明放在花括号内,并有可选的类型标注, 函数体跟在一个 -> 符号之后。如果推断出的该 lambda 的返回类型不是 Unit,那么该 lambda 主体中的最后一个(或可能是单个) 表达式会视为返回值。

val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
如果我们把所有可选标注都留下,看起来如下:
val sum = { x: Int, y: Int -> x + y }

传递末尾的 lambda 表达式

在 Kotlin 中有一个约定:如果函数的最后一个参数是函数,那么作为相应参数传入的 lambda 表达式可以放在圆括号之外

val product = items.fold(1) {acc, e-> acc * e}

这种语法也称为拖尾 lambda 表达式。

如果该 lambda 表达式是调用时唯一的参数,那么圆括号可以完全省略:

run {println("...")}

it:单个参数的隐式名称

一个 lambda 表达式只有一个参数是很常见的。 如果编译器自己可以识别出签名,也可以不用声明唯一的参数并忽略 ->。 该参数会隐式声明为 it:

ints.filter { it > 0 } // 这个字面值是“(it: Int) -> Boolean”类型的

从 lambda 表达式中返回一个值

我们可以使用限定的返回语法从 lambda 显式返回一个值。 否则,将隐式返回最后一个表达式的值。

因此,以下两个片段是等价的:

ints.filter {
    val shouldFilter = it > 0
    shouldFilter
}

ints.filter {
    val shouldFilter = it > 0
    return @filter shouldFilter
}
// 如果 lambda 表达式的参数未使用,那么可以用下划线取代其名称:
map.forEach { _, value -> println("$value!") }

匿名函数

上面提供的 lambda 表达式语法缺少的一个东西是指定函数的返回类型的能力 如果确实需要显式指定,可以使用另一种语法: 匿名函数 。

fun(x: Int, y: Int): Int = x + y

匿名函数看起来非常像一个常规函数声明,除了其名称省略了。其函数体可以是表达式(如上所示)或代码块:

fun(x: Int, y: Int): Int {
    return x + y
}

匿名函数参数总是在括号内传递。 允许将函数留在圆括号外的简写语法仅适用于 lambda 表达式

一个不带标签的 return 语句总是在用 fun 关键字声明的函数中返回。这意味着 lambda 表达式中的 return 将从包含它的函数返回,而匿名函数中的 return 将从匿名函数自身返回

闭包

外部作用域中声明的变量

// 这个sum 就是闭包 ,lambda 表达式或者匿名函数(以及局部函数和对象表达式) 可以访问其 闭包,并修改其中的值 
var sum = 0
ints.filter { it > 0 }.forEach {
    sum += it
}
print(sum)

带有接收者的函数字面值

形式为 A.(B) -> C

lambda 表达式

// 这里的other 省略的
val sum: Int.(Int) -> Int = { other -> plus(other) }

匿名函数语法允许你直接指定函数字面值的接收者类型。 如果你需要使用带接收者的函数类型声明一个变量,并在之后使用它,这将非常有用。

val sum = fun Int.(other: Int): Int = this + other
或
val sum = fun Int.(other: Int): Int { return this + other }

当接收者类型可以从上下文推断时,lambda 表达式可以用作带接收者的函数字面值(高端用法)

class HTML {
    fun body() { …… }
}

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()  // 创建接收者对象
    html.init()        // 将该接收者对象传给该 lambda
    return html
}

html {       // 带接收者的 lambda 由此开始
    body()   // 调用该接收者对象的一个方法
}