Kotlin 第十五章:高阶函数和 Lambda 表达式
高阶函数
高阶函数就是可以接受函数作为参数并返回一个函数的函数。比如 lock()
就是一个很好的例子,它接收一个 lock
对象和一个函数,运行函数并释放 lock
;
fun lock<T>(lock: Lock, body: () -> T ) : T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}
body
有一个函数类型 () -> T
,把它设想为没有参数并返回 T
类型的函数。它引发了内部的 try
函数块,并被 lock
保护,结果是通过 lock()
函数返回的。
如果我们想调用 lock()
可以使用函数引用(::
):
fun toBeSynchroized() = sharedResource.operation()
val result = lock(lock, ::toBeSynchroized)
其实最方便的办法是传递一个字面函数(通常是 lambda 表达式):
val result = lock(lock, {
sharedResource.operation() })
在 Kotlin 中有一个约定,如果最后一个参数是函数,可以省略括号:
lock (lock) {
sharedResource.operation()
}
最后一个高阶函数的例子是 map()
(of MapReduce):
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 {it -> it * 2}
如果字面函数只有一个参数,则声明可以省略,名字就是 it
:
ints map {it * 2}
这样就可以写 LINQ-风格 的代码了:
strings filter {it.length == 5} sortBy {it} map {it.toUpperCase()}
补充:it
单个参数的隐式名称
若函数参数对应的函数只有一个参数,在使用时,可以省略参数定义(连同 ->
),直接使用 it
代替参数:
字面函数和函数表达式
字面函数或函数表达式就是一个 “匿名函数”,也就是没有声明的函数,但立即作为表达式传递下去。想想下面的例子:
max(strings, {a, b -> a.length < b.length })
max
函数就是一个高阶函数,它接受函数作为第二个参数。第二个参数是一个表达式所以本生就是一个函数,即字面函数。作为一个函数,相当于:
fun compare(a: String, b: String) : Boolean = a.length < b.length
函数类型
一个函数要接受另一个函数作为参数,我们得给它指定一个类型。比如上面的 max
定义是这样的:
fun max<T> (collection: Collection<out 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
:如果第一个参数小于第二个则返回真。
在函数体第四行, less
是用作函数
一个函数类型可以像上面那样写,也可有命名参数:
val compare: (x: T, y: T) -> Int = ...
函数文本语法
一个 Lambda 表达式通常使用 { }
包围,参数是定义在 ()
内,可以添加类型注解,实体部分跟在“->
”后面;如果 Lambda 的推断返回类型不是 Unit
,那么 Lambda 主体中的最后一个(或单个)表达式将被视为返回值。
一个最普通的 Lambda 表达:
val sum: (Int, Int) -> Int = { x, y -> x + y }
使用 return
标签时,可以隐式返回最后一个表达式的值:
// 下面两个是等效的
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter
}
如果函数接受另一个函数作为最后一个参数,那么 Lambda 表达式参数可以在 ()
参数列表外部传递。
// 下面两个是等效的
lock(lock, { sharedResource.operation() })
lock (lock) {
sharedResource.operation()
}
函数表达式
上面没有讲到可以指定返回值的函数。在大多数情形中,这是不必要的,因为返回值是可以自动推断的。然而,如果你需要自己指定,可以用函数表达式来做:
fun(x: Int, y: Int ): Int = x + y
函数表达式很像普通的函数声明,除了省略了函数名。它的函数体可以是一个表达式(像上面那样)或者是一个块:
fun(x: Int, y: Int): Int {
return x + y
}
参数以及返回值和普通函数是一样的,如果它们可以从上下文推断出参数类型,则参数可以省略:
ints.filter(fun(item) = item > 0)
返回值类型的推导和普通函数一样:函数返回值是通过表达式自动推断并被明确声明
注意函数表达式的参数总是在括号里传递的。
字面函数和表达式函数的另一个区别是没有本地返回。没有 lable
的返回总是返回到 fun
关键字所声明的地方。这意味着字面函数内的返回会返回到一个闭合函数,而表达式函数会返回到函数表达式自身。
闭包
闭包是指可以包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。“闭包” 一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域)。
——来自百度百科
Lambda 表达式及匿名函数(以及局部函数,对象表达式)可以访问包含它的外部范围定义的变量(Java 中只能是常量,在 Kotlin 中可以是变量):
var sum = 0
ints.filter {
it > 0
}.forEach {
sum += it
}
print(sum)
事实上函数、Lambda、if语句、for 循环、when 语句等都是闭包,但通常情况下,我们所说的闭包是 Lambda 表达式。
闭包可以在定义的时候直接执行闭包操作,这种闭包一般用在初始化操作上:
{ x: Int, y: Int, z: String ->
println("${x + y}_ $z")
}(4, 5, "test")
像我们写构造函数的时候,主构造函数不包含任何代码,初始化代码必须写在init代码块中,而init的代码块就是闭包。
函数表达式扩展
除了普通的功能,Kotlin 支持扩展函数。这种方式对于字面函数和表达式函数都是适用的。它们最重要的使用是在 Type-safe Groovy-style builders。
表达式函数的扩展和普通的区别是它有接收类型的规范。
val sum = fun Int.(other: Int): Int = this + other
接收类型必须在表达式函数中明确指定,但字面函数不用。字面函数可以作为扩展函数表达式,但只有接收类型可以通过上下文推断出来。
表达式函数的扩展类型是一个带接收者的函数:
sum : Int.(other: Int) -> Int
可以用 .
或前缀来使用这样的函数:
1.sum(2)
1 sum 2
后记
学习完这篇文章,Kotlin 函数的学习就剩下一个内联函数的学习了,这篇文章感觉开始还算是好理解,但是到了后面,跟 Java 的差距越来越多,所以后面并不是特别的好理解,我希望每一位看我文章的童鞋都能快速的理解并且掌握,还有就是希望给看官能够多多的提出自己的宝贵意见,我们一同进步~!