高阶函数(Higher-Order Functions)
接受函数作为参数的函数,或返回值类型是函数类型的函数成为高阶函数。这个概念的很好的一个例子是lock()
函数,这个函数接收一个Lock
类型的参数(锁对象),和一个函数类型的参数:获取锁,运行函数参数,然后释放锁:
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}
分析上面的代码:body形参的类型是函数类型:() -> T
,因此该形参的实参是一个无参,返回值类型是T的函数。该函数参数在try
块中被调用,被lock
保护,且其结果被lock()
返回。
如果我们想要调用上述lock()
函数,我们可以传入已经定义好的函数作为lock()
函数的参数,如:
fun toBeSynchronized() = sharedResource.operation()
val result = lock(lock, ::toBeSynchronized)
除此之外,一个更简洁的方法是传入一个λ表达式:
val result = lock(lock, { sharedResource.operation() })
λ表达式将在下文详细介绍,但是为了更好的理解现在讲解的内容,不妨先来概述以下λ表达式的知识:
- λ表达式总是被大括号包裹
- 若存在参数,则参数在
->
之前声明,(参数类型可以省略) - 若存在函数体,则函数体在
->
之后声明
在Kotlin中,有一个常见用法:如果一个函数的最后一个参数是函数类型,且传入的实参时λ表达式,则可以在圆括号之外给出这个λ表达式:
lock (lock) {
sharedResource.operation()
}
高阶函数的另一个形象的例子是map()
函数:
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
}
该函数可以如下调用:
val doubled = ints.map { value -> value * 2 }
注意:若λ表达式是目标函数的唯一参数,则调用目标函数时,圆括号可以省略。
it:单参数的隐式表达(it: implicit name of a single parameter)
另一个有用的约定是如果一个字面函数只有一个参数,则该参数的声明连同符号->
都可以省略,以it
代指该参数:
ints.map { it * 2 }
这些约定可以写LINQ-style
风格的代码:
strings.filter { it.length == 5 }.sortBy { it }.map { it.toUpperCase() }
未使用变量的下划线代替法(Underscore for unused variables (since 1.1))
若λ表达式的参数没有使用,则可以使用下划线代替没有使用的参数名:
map.forEach { _, value -> println("$value!") }
λ表达式的解构(Destructuring in Lambdas (since 1.1))
λ表达式表达式的解构在解构声明(暂未学到)章节中讲解。
内联函数(Inline Functions)
使用内联函数有时候能提高高阶函数的性能。
λ表达式和匿名方法(Lambda Expressions and Anonymous Functions)
一个λ表达式或一个匿名函数都是一个函数字面值,也就是一个函数未声明,但立即作为表达式传递:
max(strings, { a, b -> a.length < b.length })
函数max
是一个高阶函数,因为它的第二个参数接收一个函数类型的参数。第二个参数是一个表达式,该表达式本身就是一个函数,也就是函数字面值。作为一个函数,它等同于:
fun compare(a: String, b: String): Boolean = a.length < b.length
函数类型(Function Types)
若一个函数的参数是函数类型,我们必须为该参数指定类型。例如上述函数max
定义如下:
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
类型的函数:如果第一个参数小于第二个参数的时候返回true。
在代码第四行,less被用作函数:通过传入两个T类型的参数被调用。
一个函数类型如上所写,当然也可以指定参数名称。如果想知名每一个参数含义的话:
val compare: (x: T, y: T) -> Int = ...
λ表达式表达式语法(Lambda Expression Syntax)
λ表达式表达式的完整语法,也就是函数类型变量的字面值如下:
val sum = { x: Int, y: Int -> x + y }
λ表达式总是被花括号包裹,完整语法的参数放置在圆括号内,参数可以有注解,方法体在->
符号的右边。如果推断出λ表达式的返回类型不是Unit
,则最后一个表达式将作为返回值。
如果我们补全所有可选标注,看起来如下:
val sum: (Int, Int) -> Int = { x, y -> x + y }
只有一个参数的λ表达式是非常常见的。如果Kotlin可以确定出λ表达式的签名,我们可以省略哪个唯一的参数,且将隐式为我们声明一个名为it
的参数:
ints.filter { it > 0 } // this literal is of type '(it: Int) -> Boolean'
我们可以使用特定的return语法从λ表达式显式返回一个值。否则,最后一个表达式的值将隐式返回。因此,下面两段代码是等价的:
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter
}
注意:如果一个函数的最后一个参数是函数类型,该参数可以在参数列表之外传递。
匿名函数(Anonymous Functions)
λ表达式语法缺少指定函数返回类型的能力。在大多数情况下,这不是必要的,因为返回类型可以自动的推断出来。然而,如果需要显式指定返回类型,可以使用另一个语法:匿名函数。
fun(x: Int, y: Int): Int = x + y
匿名函数看起来非常像常规函数的声明,除了没有函数名。匿名函数的函数体可以是表达式也可以是代码块:
fun(x: Int, y: Int): Int {
return x + y
}
参数可返回值的指定与常规函数一样,但参数类型可以推断出来的时候,参数类型是可以省略的:
ints.filter(fun(item) = item > 0)
匿名函数的返回类型工作机制就像常规函数一样:对于函数体是表达式的,可以自动推断;对于函数体是代码块的,则必须显式指定(除了Unit)。
注意:实参时匿名函数的参数总是传递到括号内。将函数留在圆括号外的简介语法仅仅适用于λ表达式。
λ表达式和匿名函数的另一个不同是非局部返回的行为。没有标签的return语句总是返回至使用fun关键字的函数声明处。这意味着λ表达式内部的return将返回至包含它的封闭函数出;而匿名函数的return将返回至匿名函数本身。
闭包(Closures)
λ表达式或匿名函数(无论是作为局部函数还是对象表达式)都可以访问其闭包,即在外部作用于中声明的变量。不同于Java,闭包中的变量可以被修改:
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
print(sum)
带接收者的函数字面值(Function Literals with Receiver)
Kotlin可以为函数字面值提供接收者对象。在函数字面值的函数体内部,可以无需任何额外的修饰符就调用接收者对象的方法。这与扩展函数是类似的,允许在函数体内部访问接收者对象的成员。其用法的主要例子是类型安全的Groovy-风格构建起。
这样的函数字面的类型是带有接收器的函数类型:
sum : Int.(other: Int) -> Int
上述函数字面可以像接收者对象自身的函数一样调用:
1.sum(2)
匿名函数语法允许直接指定函数字面的接收者类型。如果需要为函数声明一个变量,并使用它,这将非常有用:
val sum = fun Int.(other: Int): Int = this + other
当接收者类型可以从上下文推断出来的话,λ表达式可以用作带有接收者的函数字面值:
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
}