🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 高阶函数与 Lambda ## 高阶函数 所谓高阶函数就是有函数作为参数的函数,或是返回一个函数。这种函数的一个好的例子就是 `lock()`,带有一个 lock 对象和一个函数,获得 lock,运行函数然后释放这个 lock: ``` kotlin fun <T> lock(lock: Lock, body: () -> T): T { lock.lock() try { return body() } finally { lock.unlock() } } ``` 让我们审视一下上面的代码:`body` 有一个[函数类型](#function-types) `() -> T`,所以假定它是一个函数不带参数并返回一个类型为 `T` 的值,它在 `try` 块中被调用,又被 `lock` 所保护,而且它的结果通过 `lock()` 函数返回。 如果我们要调用 `lock()`,我们可以传递不同的函数作为实参给它(查看[函数引用](reflection.html#function-references)): ``` kotlin fun toBeSynchronized() = sharedResource.operation() val result = lock(lock, ::toBeSynchronized) ``` 另外,一般更方便的方式是传递一个 [lambda 表达式](#lambda-expressions-and-anonymous-functions): ``` kotlin val result = lock(lock, { sharedResource.operation() }) ``` lambda 表达式在下面有更[详细的描述](#lambda-expressions-and-anonymous-functions),但为了本节内容的连续性,让我们概览一下: * 一个 lambda 表达式始终围绕着花括号, * 它的参数(如果有的话)在 `->` 的前面声明(参数的类型可以被推断出), * 主体在 `->` 后面(如果有的话) 在 Kotlin 里面,有一个约定,如果最后那个函数参数是一个函数,那么那个函数可以在圆括号外面指定: ``` kotlin lock (lock) { sharedResource.operation() } ``` 另一个高阶函数的例子应该是 `map()`: ``` kotlin fun <T, R> List<T>.map(transform: (T) -> R): List<R> { val result = arrayListOf<R>() for (item in this) result.add(transform(item)) return result } ``` 这个函数可以在下面调用: ``` kotlin val doubled = ints.map { it -> it * 2 } ``` 注意如果 lambda 是调用时仅有的实参,那么圆括号在调用时完全可以省略。 另一个很有帮助的约定是如果一个函数字面上只有一个参数,它的声明可以省略(`->` 顺道省略),并且它的名称将是 `it`: ``` kotlin ints.map { it * 2 } ``` 这些约定允许书写 [LINQ 风格](http://msdn.microsoft.com/en-us/library/bb308959.aspx)的代码: ``` kotlin strings.filter { it.length == 5 }.sortBy { it }.map { it.toUpperCase() } ``` ## 内联函数 有时使用[内联函数](inline-functions.html)对提升高阶函数的性能很有好处。 ## lambda 表达式与匿名函数 lambda 表达式或才匿名函数是“函数字面值”,意即一个未经声明函数,但被隐含地作为一个表达式传递。思考下面的示例: ``` kotlin max(strings, { a, b -> a.length() < b.length() }) ``` 函数 `max` 是一个高阶函数,意即它带有一个函数值作为第二个实参。这第二个实参是一个自身为函数的表达式,意即一个函数字面值。作为一个函数它等价于 ``` kotlin fun compare(a: String, b: String): Boolean = a.length() < b.length() ``` ### 函数类型 要一个函数作为参数接受另一个函数,我们得为那个参数指定一个函数类型。例如上述的函数 `max` 定义如下: ``` kotlin fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? { var max: T? = null for (it in collection) if (max == null || less(max, it)) max = it return max } ``` 参数 `less` 的类型是 `(T, T) -> Boolean`,意即一个函数带有两个类型为 `T` 的参数并返回一个 `Boolean`:如果第一个比第二个小则为真。 主体第 4 行,`less` 作为一个函数使用:它通过传递两个类型为 `T` 的实参来调用。 一个函数类型要像上面那样书写,或者如果你要记录每个参数的意义的话,也可以有命名的参数。 ``` kotlin val compare: (x: T, y: T) -> Int = ... ``` ### lambda 表达式语法 lambda 表达式完整的语法形式,即函数类型的文本,是像下面这样的: ``` kotlin val sum = { x: Int, y: Int -> x + y } ``` lambda 表达式始终以花括号包围,完整语法形式中参数声明在圆括号里面并拥有可选的注解,主体则在一个 `->` 符号后面。如果可选的注解我们全部不要,看上去就是这样的: ``` kotlin val sum: (Int, Int) -> Int = { x, y -> x + y } ``` lambda 表达式通常只有一个参数。如果 Kotlin 能推测它自身的签名,就能允许我们不声明这个仅有的参数,而是隐含地为我们把声明它为 `it`: ``` kotlin ints.filter { it > 0 } // 字面类型为 '(it: Int) -> Boolean' ``` 注意如果一个函数带了其它函数作为最后一个参数,那么这个 lambda 表达式实参可以在参数列表的括号外面传递。查看语法入门的[调用后缀](grammar.html#call-suffix)。 ### 匿名函数 上面 lambda 表达式语法唯一不能展现的技能就是函数的返回类型。在大多数情况下并无此必要,因为返回类型可以自动推断。然而,如果你需要明确地指定它,你可以使用一种替代语法:一个_匿名函数_。 ``` kotlin fun(x: Int, y: Int): Int = x + y ``` 除非忽略它的名称之外,匿名函数看上去十分像普通函数声明。它的主体既可以是一个表达式(如上面所展示的一样),又可以是一个块: ``` kotlin fun(x: Int, y: Int): Int { return x + y } ``` 参数和返回类型的指定方式与普通函数一样,除了在参数类型能从上下文推断出时可以被省略之外: ``` kotlin ints.filter(fun(item) = item > 0) ``` 匿名函数的返回类型推断如普通函数一样:返回类型会自动地从表达式主体推断出,而必须为带有块体的匿名函数明确地指出(或假定为 `Unit`)。 注意匿名函数参数始终在圆括号里面传递。允许离开函数圆括号外部的简写语法只针对 lambda 表达式。 一个其它不同于 lambda 表达式和匿名函数的是[非本地返回](inline-functions.html#non-local-returns)的行为。一个不带标签的 `return` 指令总是从 `func` 关键字声明的地方返回。这个意思就是一个 lambda 表达式里的 `return` 将从函数闭包返回,而匿名 函数的 `return` 将从这个匿名函数自身返回。 ### 闭包 一个 lambda 表达式或匿名函数(与[局部函数](functions.html#local-functions)和[对象表达式](object-declarations.html#object-expressions)一样)可以访问它的_闭包_,意即这个在外部作用域声明的变量。与 Java 不同,这个在闭包内捕获的变量可以被修改: ``` kotlin var sum = 0 ints.filter { it > 0 }.forEach { sum += it } print(sum) ``` ### 带接受者的函数字面量 Kotlin 提供了调用指定了_接受者对象_的函数字面量的功能。在函数字面量的主体中,你可以在接受者对象上调用方法而无须什么附加的限定符。这与扩展函数类似,允许你在函数主体内部访问接受者对象的成员。它们使用的一个很重要的例子是[类型安全的 Groovy 风格构建器](type-safe-builders.html) 这样的函数字面量的类型为一个带有接受者的函数类型: ``` kotlin sum : Int.(other: Int) -> Int ``` 这个函数字面量可以在接受者对象上像方法一样被调用: ``` kotlin 1.sum(2) ``` 匿名函数语法允许你直接指定一个函数字面量的接受者类型。如果你需要声明一个带有接受者的函数类型的变量并在以后使用它,这会很有用。 ``` kotlin val sum = fun Int.(other: Int): Int = this + other ``` 在接受者类型能够从上下文推断出来时,lambda 表达式可以像带接受者的函数字面量一样使用。 ``` kotlin class HTML { fun body() { ... } } fun html(init: HTML.() -> Unit): HTML { val html = HTML() // 创建接受者对象 html.init() // 传递接受者对象到 lambda return html } html { // 带接受者的 lambda body() // 在接受者对象上调用一个方法 } ```