Kotlin学习之-6.2 高阶函数和lambda表达式

时间:2021-02-20 19:11:49

Kotlin学习之-6.2 高阶函数和lambda表达式

高阶函数

一个高阶函数是一个接受函数作为参数,或者返回一个函数。一个高阶函数的例子是lock()函数,它接受一个锁对象和一个函数,获取锁,运行函数,然后释放锁。

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

上述代码中,body是函数类型:() -> T, 因此这是一个不接受任何参数并且返回T类型的函数。它在try块中被调用,被lock守护,并且结果通过lock()函数返回。

如果我们想要调用lock(),我们可以调用另外一个函数给它当参数。

fun toBeSynchronized() = shareResource.operation()

val result = lock(lock, ::toBeSynchronized())

另一种更便捷的方式是传递一个lambda表达式

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

更多lambda表达式的细节在下面展开,但是为了继续本节内容,先做一个简要的总结:

  • 一个lambda表达式通常用花括号括起来。
  • 如果有参数的话,定义在->之前,参数类型可以省略。
  • 主体在->之后。

Kotlin中有一个规范,如果函数的最后一个参数是一个函数,那么可以在括号外面,通过传递一个lambda表达式当做对应的参数。例如,上述lock代码可以写成这样

lock (lock) {
sharedResource.operation()
}

另外一个高阶函数的例子是map()

fun <T<R> List<T>.map(transform: (T> -> R): List<R> {
val result = arrayListOf<R>()
for (item in this)
retult.add(transform(item))
return result
}

这个函数可以这样调用:

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

注意如果lambda表达式是函数唯一的参数,那么调用中时括号可以省略

it 隐式指代单一参数

另一个有用的规范是,如果一个函数只有一个参数,那么他的定义(和->)可以被省略,并且命名成it

ints.map { it * 2 }

这些规范允许写成LINQ风格的代码:

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

不使用的变量使用下划线(从Kotlin v1.1)

如果lambda参数没有使用,你可以放置一个下划线来代替他的名字

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

lambda表达式的解构

内联函数

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

lambda表达式和匿名函数

一个lambda表达式或者一个匿名函数式一个函数语法,例如,一个函数没有声明但是直接被当做表达式来传递。考虑下面的例子:

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)
it (max == null || less(max,it))
max = it
return max
}

参数less的类型是(T, T) -> Boolean,它是一个函数,接受两个T类型的参数,并且返回一个Boolean,当第一个值较小时返回true。
在第4行中,less当做函数使用:调用时传递了两个T类型的参数
一个函数类型可以像上面那样写,也可以有命名参数,你可以记录每一个参数的意义。

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

lambda表达式语法

lambda表达式完整的语法形式,如下所示:

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

一个lambda表达式通常用花括号括起来,在完整语法形式下参数定义在括号里面并且有可选的类型注解,主体在->标识后面。 如果推断出的lambda表达式返回值不是Unit,那么lambda表达式主体中最后一个表达式就被当做返回值。

如果把所有可选的注解都去掉,剩下的就是这样:

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

lambda表达式仅有一个参数是很常见的。如果Kotlin可以自己推断出来的话,它允许我们不用定义这个仅有的参数,并会隐式地为我们用it作为名字来定义它。

ints.filter { it > 0 }
// 这相当于 (it: Int) -> Boolean = { it > 0}

我们可以使用指定返回qualified return语法来显式地从一个lambda表达式返回一个值。否则,最后一个语句的值将被隐式的返回。因此下面两个代码段是等价的。

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表达式时,可以使用简写语法让函数在括号外传递。

还有一个lambda表达式和匿名函数之间的区别是被局部返回值的行为。一个不带标签的return语句总是从一个用fun关键字定义的函数中返回。这以为着在lambda表达式中return会从外层的函数中返回,而在匿名函数中的return会从匿名函数中返回。

闭包(Colsures)

一个lambda表达式或者匿名函数(还有局部函数和对象表达式)可以访问它的闭包,例如,定义在外层的变量。 这和Java不同(Java中要使用final,且不能修改),定义在比包中的变量可以被修改。

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

带接收器的函数语法

Kotlin提供在调用一个函数时指定一个接收器对象. 在函数体内,你可以调用接收器对象的方法且不需要任何附加的修饰符。这和扩展函数很类似,这让你可以在函数体内访问接收器对象的成员。一个重要的例子是类型安全的Groovy风格构造器

这样的带有接收器的函数语法如下:

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

这个函数可以当做踏实一个接收器对象的函数来调用

1.sum(2)

匿名函数语法让你可以直接指定函数的接收器类型。当你需要先定义一个函数类型的接收器并在后续使用它的时候非常有用。

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

lambda表达式在接收器类型可以从上下文中推断出来的时候,可以当做带有接收器的函数来使用。

class HTML {
fun body() { ... }
}

fun html(init: HTML.() -> Unit): HTML {
val html = HTML()
html.init()
return html
}

html {
body()
}

PS,我会坚持把这个系列写完,有问题可以留言交流,也关注专栏Kotlin for Android Kotlin安卓开发