Kotlin Reference: Higher-Order Functions and Lambdas
Contents
Higher-Order Functions
高阶函数指的是以函数作为参数或返回值的函数。lock()
函数是一个很好的例子,它接受一个对象和一个函数作为参数,获取对象的锁后,执行函数并释放锁:
[code lang=”kotlin”]fun
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 <T, R> List<T>.map(transform: (T) -> R): List<R> {
val result = arrayListOf<R>()
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 -> a.length < b.length })[/code]
函数 max
是一个高阶函数,它的第二个参数是一个函数。上面的用法中,max
的第二个参数是是一个表达式,该表达式本身是一个函数,即函数字面值;作为一个函数,它等效于:
[code lang=”kotlin”]fun compare(a: String, b: String): Boolean = a.length < b.length[/code]
Function Types
对于一个接受其他函数作为参数的函数,我们必须为该参数指定一个函数类型,如前面提到的 max
定义如下:
[code lang=”kotlin”]fun
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) -> Int = …[/code]
Lambda Expression Syntax
Lambda 表达式(即函数类型字面值)的完整句法如下:
[code lang=”kotlin”]val sum = { x: Int, y: Int -> x + y }[/code]
Lambda 表达式始终被大括号包围。完整句法形式的参数声明写在括号内,且有可选的类型标注。函数体写在 ->
符号后面。如果推断 Lambda 的返回值不是 Unit
,则 Lambda 体内最后一个(也可能是唯一一个)表达式的值会作为函数的返回值。
如果省略掉所有的可选标注,剩下的部分如下:
[code lang=”kotlin”]val sum = { x: Int, y: Int -> x + y }[/code]
只有一个参数的 Lambda 表达式十分常见,如果 Kotlin 能够自己判断出签名,则可以把这唯一的参数声明也省略掉,该参数会被隐式地声明为 it
:
[code lang=”kotlin”]ints.filter { it > 0 } // this literal is of type ‘(it: Int) -> Boolean'[/code]
可以使用限定的返回 语法显式地从 Lambda 中返回值,否则 Lambda 中的最后一个表达式会被隐式地返回,因此,下面两段代码是等效的:
[code lang=”kotlin”]ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 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 > 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) -> 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.() -> 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]