Kotlin进阶之高阶函数与λ表达式(Higher-Order Functions and Lambdas)

高阶函数(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() })

λ表达式将在下文详细介绍,但是为了更好的理解现在讲解的内容,不妨先来概述以下λ表达式的知识:

  1. λ表达式总是被大括号包裹
  2. 若存在参数,则参数在->之前声明,(参数类型可以省略)
  3. 若存在函数体,则函数体在->之后声明

在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
}
    原文作者:键笔刀
    原文地址: https://www.jianshu.com/p/966ea60fb787
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞