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
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容