高阶函数
高阶函数是将函数用作参数或返回值的函数。 例:
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() // 调用该接收者对象的一个方法
}