Kotlin Reference: Higher-Order Functions and Lambdas

Higher-Order Functions

  高阶函数指的是以函数作为参数或返回值的函数。lock() 函数是一个很好的例子,它接受一个对象和一个函数作为参数,获取对象的锁后,执行函数并释放锁:

[code lang=”kotlin”]fun lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}[/code]

来看一下上面的代码:body函数类型() -> T,这个函数没有参数,有一个类型为 T 的返回值。它被 lock 保护,在 try 块中被调用,其结果由 lock() 函数返回。

  如果要调用 lock() 函数,可以向其传递另一个函数作为参数(详见函数引用)。

[code lang=”kotlin”]fun toBeSynchronized() = sharedResource.operation()

val result = lock(lock, ::toBeSynchronized)[/code]

  另外,使用 Lambda 表达式通常会更加方便:

[code lang=”kotlin”]val result = lock(lock, { sharedResource.operation() })[/code]

  Lambda 表达式的内容详见下面,但为了继续说明这一节的内容,我们先来看一下它的其简介:

  • Labmda 表达式总要被大括号包围,
  • 其参数(如果有的话)在 -> 的左边声明(参数类型可以省略),
  • 函数体(如果有的话)写在 -> 的右边。

  如果函数的最后一个参数是函数类型,且你传递的是 Lambda 表达式,Kotlin 的惯例是把 Lambda 表达式写在括号外面:

[code lang=”kotlin”]lock (lock) {
sharedResource.operation()
}[/code]

  高阶函数的另一个例子是 map()

[code lang=”kotlin”]fun &ltT, R&gt List&ltT>.map(transform: (T) -&gt R): List&ltR&gt {
val result = arrayListOf&ltR&gt()
for (item in this)
result.add(transform(item))
return result
}[/code]

map() 的调用方法如下:

[code lang=”kotlin”]val doubled = ints.map { value -> value * 2 }[/code]

  注意如果 Lambda 表达式是函数调用的唯一参数,则可以省略函数调用时的括号。

it: implicit name of a single parameter

  另一个有用的惯例是,如果字面函数只有一个参数,则可以省略其声明(以及 ->),此时参数的名称是 it

[code lang=”kotlin”]ints.map { it * 2 }[/code]

  使用这些惯例,可以写出 LINQ 风格的代码:

[code lang=”kotlin”]strings.filter { it.length == 5 }.sortBy { it }.map { it.toUpperCase() }[/code]

Underscore for unused variables (since 1.1)

  如果没有用到 Lambda 表达式的某个参数,可以使用下划线替代其名称:

[code lang=”kotlin”]map.forEach { _, value -> println(“$value!”) }[/code]

Destructuring in Lambdas (since 1.1)

  在 Lambda 中使用解构的方法详见 解构声明

Inline Functions

  有时候,使用内联函数可以提高高阶函数的性能。

Lambda Expressions and Anonymous Functions

  Lambda 表达式和匿名函数属于“函数字面值(Function Literal)”,也就是没有经过声明而直接作为表达式传递的函数,考虑下面的例子:

[code lang=”kotlin”]max(strings, { a, b -&gt a.length &lt b.length })[/code]

函数 max 是一个高阶函数,它的第二个参数是一个函数。上面的用法中,max 的第二个参数是是一个表达式,该表达式本身是一个函数,即函数字面值;作为一个函数,它等效于:

[code lang=”kotlin”]fun compare(a: String, b: String): Boolean = a.length &lt b.length[/code]

Function Types

  对于一个接受其他函数作为参数的函数,我们必须为该参数指定一个函数类型,如前面提到的 max 定义如下:

[code lang=”kotlin”]fun max(collection: Collection&ltT&gt, less: (T, T) -&gt Boolean): T? {
var max: T? = null
for (it in collection)
if (max == null || less(max, it))
max = it
return max
}[/code]

参数 less 的类型是 (T, T) -> Boolean,也就是接受两个类型为 T 的参数并返回 Boolean 的函数:如果第一个参数小于第二个,则返回 true。在第 4 行的函数体中,可以像函数一样调用 less,传递给它的是两个类型为 T 的参数。

  除了像上面的书写方式,函数类型也可以有命名参数,可以记录各个参数的含义:

[code lang=”kotlin”]val compare: (x: T, y: T) -&gt Int = …[/code]

Lambda Expression Syntax

  Lambda 表达式(即函数类型字面值)的完整句法如下:

[code lang=”kotlin”]val sum = { x: Int, y: Int -&gt x + y }[/code]

  Lambda 表达式始终被大括号包围。完整句法形式的参数声明写在括号内,且有可选的类型标注。函数体写在 -> 符号后面。如果推断 Lambda 的返回值不是 Unit,则 Lambda 体内最后一个(也可能是唯一一个)表达式的值会作为函数的返回值。

  如果省略掉所有的可选标注,剩下的部分如下:

[code lang=”kotlin”]val sum = { x: Int, y: Int -&gt x + y }[/code]

  只有一个参数的 Lambda 表达式十分常见,如果 Kotlin 能够自己判断出签名,则可以把这唯一的参数声明也省略掉,该参数会被隐式地声明为 it

[code lang=”kotlin”]ints.filter { it &gt 0 } // this literal is of type ‘(it: Int) -> Boolean'[/code]

  可以使用限定的返回 语法显式地从 Lambda 中返回值,否则 Lambda 中的最后一个表达式会被隐式地返回,因此,下面两段代码是等效的:

[code lang=”kotlin”]ints.filter {
val shouldFilter = it &gt 0
shouldFilter
}

ints.filter {
val shouldFilter = it &gt 0
return@filter shouldFilter
}[/code]

  注意如果一个函数以另一个函数作为最后一个参数,则以 Lambda 表达式为该参数时,可以在参数列表的括号外面传递,相关语法详见 callSuffix

Anonymous Functions

  上面给出的 Lambda 表达式语法缺少指定函数返回值类型的能力,在多数情况下,这并没有必要,因为返回值类型可以被自动推断出来。但是如果需要显式地指定返回值类型,可以使用另外一种语法:匿名函数(Anonymous Function)

[code lang=”kotlin”]fun(x: Int, y: Int): Int = x + y[/code]

  匿名函数看上去非常像常规的函数声明,但它省略了函数名。匿名函数的函数体可以是表达式(如上)或代码块:

[code lang=”kotlin”]fun(x: Int, y: Int): Int {
return x + y
}[/code]

  匿名函数可以像常规的函数一样指定参数和返回值,不同之处在于,如果参数类型可以通过上下文被推断出来,就可以省略参数类型:

[code lang=”kotlin”]ints.filter(fun(item) = item > 0)[/code]

  匿名函数返回值类型的推断和普通函数一样:如果匿名函数的函数体是表达式,则返回值类型会被自动推断;如果匿名函数的函数体是代码块,则必须显式地指明返回值类型(不指定则假定为是 Unit)。

  注意匿名函数的参数总是在括号内传递,在括号外传递函数的简写语法仅适用于 Lambda 表达式。

  匿名函数和 Lambda 表达式的另一个区别是非局部返回(Non-Local Returns)的行为。没有标签的 return 总是会让使用 fun 关键字声明的函数返回,这意味着 Lambda 表达式中的 return 会让包围在外部的函数返回,而匿名函数中的 return 会让匿名函数本身返回。

Closures

  Lambda 表达式和匿名函数(以及局部函数对象表达式)可以访问其闭包(Closure),也就是在外部作用域声明的变量。不同于 Java 的是,可以修改从闭包中获取的变量:

[code lang=”kotlin”]var sum = 0
ints.filter { it &gt 0 }.forEach {
sum += it
}
print(sum)[/code]

Function Literals with Receiver

  Kotlin 提供了在调用函数字面值时指定接收者对象(Receiver Object)的能力。在函数字面值内部,可以直接调用接收对象的方法,而不必使用额外的限定符。这类似于扩展函数,扩展函数内部也可以访问接收对象的成员。这一功能最重要的用途之一是实现类型安全的 Groovy 风格建造者(Type-Safe Groovy-Style Builders)

  此类函数字面值的类型是带有接收者的函数类型:

[code lang=”kotlin”]sum : Int.(other: Int) -&gt Int[/code]

可以像调用接收者对象的方法一样调用该函数字面值:

[code lang=”kotlin”]1.sum(2)[/code]

  匿名函数的语法允许直接指定函数字面值的接收对象的类型,这样就可以先声明一个带有接收者的函数变量,以便后续使用:

[code lang=”kotlin”]val sum = fun Int.(other: Int): Int = this + other[/code]

  当接收类型可以从上下文推断出来时,Lambda 表达式也可以用作带接收者的函数字面值。

[code lang=”kotlin”]class HTML {
fun body() { … }
}

fun html(init: HTML.() -&gt Unit): HTML {
val html = HTML() // create the receiver object
html.init() // pass the receiver object to the lambda
return html
}

html { // lambda with receiver begins here
body() // calling a method on the receiver object
}[/code]