参考:

高阶函数

一个高阶函数接收函数作为其参数,或者返回一个函数。举个栗子,如下所示,lock()函数接收一个lock对象和一个函数,获取锁,运行函数,然后释放锁:

    fun <T> lock(lock: Lock, body: () -> T): T {
        lock.lock()
        try {
            return body()
        }
        finally {
            lock.unlock()
        }
    }

body有一个函数类型:() -> T,它是一个返回类型为T的值的方法。

如果我们想调用lock(),我们可以将另一个函数作为参数传递给它(参考:方法引用):

    fun toBeSynchronized() = sharedResource.operation()

    val result = lock(lock, ::toBeSynchronized)

此外,一个更加便捷的方式是传递一个lambda表达式:

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

Lambda表达式先简要介绍下,后面小节会有详细描述:

  • 一个lambda表达式总是由花括弧包围
  • 它的参数在记号->前声明,参数类型可以忽略
  • 正文跟在->

在Kotlin中如果一个函数的最后一个参数是一个函数,并且使用lambda表达式传递对应的参数,有一个简便的方式来表示,即将lambda表达式放在函数参数列表括弧外:

    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 }

语法糖

如果lambda表达式是函数的唯一参数,那么参数括弧也可以省略。

另一个惯用法是,如果函数仅有一个参数,该参数的声明(以及后面的->)可以忽略,并使用it代表该参数:

    ints.map { it * 2 }

它允许编写LINQ样式的代码:

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

如果lambda参数未使用,你可以使用一个下划线声明:

    map.forEach { _, value -> println("$value!") }

Lambda表达式与匿名函数

一个lambda表达式或匿名函数是一个“函数字面量(function literal)”,也就是未声明的函数,作为表达式直接传递。考虑如下例子:

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

函数max是一个高阶函数,它接收一个函数值作为第二个参数,第二个参数是自身为函数的一个表达式,也就是函数字面量。作为一个函数,它等价于:

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

函数类型

为了让一个函数接收另一个函数作为参数,必须为接收的参数指定一个函数类型,上面的例子中,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。

函数类型中的参数可以命名:

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

如果声明了一个可以为空的函数类型变量,将函数类型用括弧包围并加一个问号:

    var sum: ((Int, Int) -> Int)? = null

Lambda表达式语法

Lambda表达式的完整语法形式如下:

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

一个Lambda表达式总是使用花括弧包围,参数声明在花括号中,可以有可选的类型标识,正文跟在右箭头符号->后。如果推断出lambda返回类型不是Unit,那么最后一个表达式就是返回值。

如果将可选的标识移到外面,可以表示如下:

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

我们可以使用限定的return语法从lambda中显式地返回一个值,否则会返回最后一个表达式的值,下面两种表示是等价的:

    ints.filter {
        val shouldFilter = it > 0 
        shouldFilter
    }

    ints.filter {
        val shouldFilter = it > 0 
        return@filter shouldFilter
    }

匿名函数

上面的lambda表达式中不能指定函数的返回类型,大多数情况下,可以进行自动推断。然而,如果你想特别指定,可以使用匿名函数:

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

    fun(x: Int, y: Int): Int {
        return x + y
    }

参数和返回类型的指定和常规函数一样,除了一点以外,即匿名函数参数类型如果可以从上下文中推断出,那么可以将其忽略:

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

匿名函数和lambda表达式的不同之处在于它不能作为括弧外参数传递,此外,两者的非局部返回(non-local returns)行为也不一样,一个无标签的return总是从fun声明的函数内返回,这表明lambda表达式中的return会从包围函数返回,而匿名函数会从自身返回。

闭包(Closures)

一个lambda表达式或者匿名函数(以及局部函数和对象表达式)可以访问其闭包,也就是在外部范围声明的变量。

    var sum = 0
    ints.filter { it > 0 }.forEach {
        sum += it
    }
    print(sum)

带接收器的函数字面量

Kotlin允许用一个指定的接收对象调用一个函数字面量。在函数字面量的正文中,你可以直接调用接收对象的方法。这有点类似扩展函数,它允许你在函数体重访问接收对象的成员。

这种类型的函数字面量是一个带接收器的函数类型:

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

函数字面量可以像接受对象的成员一样进行调用:

    1.sum(2)

你们函数语法允许你直接指定一个函数字面量的接收器类型。如果你需要声明一个带接收器的函数类型变量,这就十分有用:

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

对于一个带接收器函数类型的非字面量值,在一个接收常规函数作为参数的地方并且该常规函数类型第一个参数与该值的接收器类型一样,那么该值可以作为参数传递,反之亦然。例如,类型String.(Int) -> Boolean(String, Int) -> Boolean是兼容的:

    val represents: String.(Int) -> Boolean = { other -> toIntOrNull() == other }
    println("123".represents(123)) // true

    fun testOperation(op: (String, Int) -> Boolean, a: String, b: Int, c: Boolean) =
        assert(op(a, b) == c)
        
    testOperation(represents, "100", 100, true) // OK

当接收器类型可以从上下文中推断出时,Lambda表达式可以用作带接收器的函数字面量:

    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
    }