高阶函数与 lambda 表达式

高阶函数

高阶函数就是可以接受函数作为参数或者返回一个函数的函数。比如 lock() 就是一个很好的例子,它接收一个 lock 对象和一个函数,运行函数并释放 lock;

  1. fun <T> lock(lock: Lock, body: () -> T ) : T {
  2. lock.lock()
  3. try {
  4. return body()
  5. }
  6. finally {
  7. lock.unlock()
  8. }
  9. }

现在解释一下上面的代码吧:body 有一个函数类型 () -> T,把它设想为没有参数并返回 T 类型的函数。它引发了内部的 try 函数块,并被 lock 保护,结果是通过 lock() 函数返回的。

如果我们想调用 lock() ,函数,我们可以传给它另一个函数做参数,参看函数引用

  1. fun toBeSynchroized() = sharedResource.operation()
  2. val result = lock(lock, ::toBeSynchroized)

其实最方便的办法是传递一个字面函数(通常是 lambda 表达式):

  1. val result = lock(lock, {
  2. sharedResource.operation() })

字面函数经常描述有更多细节,但为了继续本节,我们看一下更简单的预览吧:

字面函数被包在大括号里

参数在 -> 前面声明(参数类型可以省略)

函数体在 -> 之后

在 kotlin 中有一个约定,如果某一个函数的最后一个参数是函数,并且你向那个位置传递了一个 lambda 表达式,那么,你可以在括号外面定义这个 lambda 表达式:

  1. lock (lock) {
  2. sharedResource.operation()
  3. }

最后一个高阶函数的例子是 map() (of MapReduce):

  1. fun <T, R> List<T>.map(transform: (T) -> R):
  2. List<R> {
  3. val result = arrayListOf<R>()
  4. for (item in this)
  5. result.add(transform(item))
  6. return result
  7. }

函数可以通过下面的方式调用

  1. val doubled = ints.map {it -> it * 2}

如果字面函数只有一个参数,则声明可以省略,名字就是 it :

  1. ints.map {it * 2}

这样就可以写LINQ-风格的代码了:

  1. strings.filter{ it.length == 5 }.sortedBy{ it }.map{ it.toUpperCase() }

内联函数

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

字面函数和函数表达式

字面函数或函数表达式就是一个 “匿名函数”,也就是没有声明的函数,但立即作为表达式传递下去。想想下面的例子:

  1. max(strings, {a, b -> a.length < b.length })

max 函数就是一个高阶函数,它接受函数作为第二个参数。第二个参数是一个表达式所以本生就是一个函数,即字面函数。作为一个函数,相当于:

  1. fun compare(a: String, b: String) : Boolean = a.length < b.length

函数类型

一个函数要接受另一个函数作为参数,我们得给它指定一个类型。比如上面的 max 定义是这样的:

  1. fun max<T>(collection: Collection<out T>, less: (T, T) -> Boolean): T? {
  2. var max: T? = null
  3. for (it in collection)
  4. if (max == null || less(max!!, it))
  5. max = it
  6. return max
  7. }

参数 less(T, T) -> Boolean类型,也就是接受俩个 T 类型参数返回一个 Boolean:如果第一个参数小于第二个则返回真。

在函数体第四行, less 是用作函数

一个函数类型可以像上面那样写,也可有命名参数,更多参看命名参数

  1. val compare: (x: T,y: T) -> Int = ...

函数文本语法

函数文本的完全写法是下面这样的:

  1. val sum = {x: Int,y: Int -> x + y}

函数文本总是在大括号里包裹着,在完全语法中参数声明是在括号内,类型注解是可选的,函数体是在 -> 之后,像下面这样:

  1. val sum: (Int, Int) -> Int = {x, y -> x+y }

函数文本有时只有一个参数。如果 kotlin 可以从它本生计算出签名,那么可以省略这个唯一的参数,并会通过 it 隐式的声明它:

  1. ints.filter {it > 0}//这是 (it: Int) -> Boolean 的字面意思

注意如果一个函数接受另一个函数做为最后一个参数,该函数文本参数可以在括号内的参数列表外的传递。参看 callSuffix

函数表达式

上面没有讲到可以指定返回值的函数。在大多数情形中,这是不必要的,因为返回值是可以自动推断的。然而,如果你需要自己指定,可以用函数表达式来做:

  1. fun(x: Int, y: Int ): Int = x + y

函数表达式很像普通的函数声明,除了省略了函数名。它的函数体可以是一个表达式(像上面那样)或者是一个块:

  1. fun(x: Int, y: Int): Int {
  2. return x + y
  3. }

参数以及返回值和普通函数是一样的,如果它们可以从上下文推断出参数类型,则参数可以省略:

  1. ints.filter(fun(item) = item > 0)

返回值类型的推导和普通函数一样:函数返回值是通过表达式自动推断并被明确声明

注意函数表达式的参数总是在括号里传递的。 The shorthand syntax allowing to leave the function outside the parentheses works only for function literals.

字面函数和表达式函数的另一个区别是没有本地返回。没有 lable 的返回总是返回到 fun 关键字所声明的地方。这意味着字面函数内的返回会返回到一个闭合函数,而表达式函数会返回到函数表达式自身。

闭包

一个字面函数或者表达式函数可以访问闭包,即访问自身范围外的声明的变量。不像 java 那样在闭包中的变量可以被捕获修改:

  1. var sum = 0
  2. ints.filter{it > 0}.forEach {
  3. sum += it
  4. }
  5. print(sum)

函数表达式扩展

除了普通的功能,kotlin 支持扩展函数。这种方式对于字面函数和表达式函数都是适用的。它们最重要的使用是在 Type-safe Groovy-style builders

表达式函数的扩展和普通的区别是它有接收类型的规范。

  1. val sum = fun Int.(other: Int): Int = this + other

接收类型必须在表达式函数中明确指定,但字面函数不用。字面函数可以作为扩展函数表达式,但只有接收类型可以通过上下文推断出来。

表达式函数的扩展类型是一个带接收者的函数:

  1. sum : Int.(other: Int) -> Int

可以用 . 来使用这样的函数:

  1. 1.sum(2)