闭包
闭包就是包含函数的运行环境,可以在函数里定义类,闭包指向的函数运行环境不会被回收。
fun makeFun(): () -> Unit {
var count = 0
return fun() {
println(++count)
}
}
fun main(args: Array<String>) {
val x = makeFun()
x() // 1
x() // 2
x() // 3
x() // 4
x() // 5
}
通过上面的例子可以看出makeFun方法返回的其实是一个函数,而makeFun函数的局部变量count保存着当前的运算值,通过不断的调用返回的函数count的值也随之不断增加。这说明count在函数被调用完成后并未被回收依然保存在内存中,以后再调用都会使用同一个内存变量。
fun fibonacci(): () -> Long {
var first = 0L
var second = 1L
return fun(): Long {
val result = second
second += first
first = second - first
println(result)
return result
}
}
fun main(args: Array<String>) {
val x = fibonacci()
x() // 1
x() // 1
x() // 2
x() // 3
x() // 5
x() // 8
}
这个斐波那契数列实现也是在外部的函数里包含了局部变量,返回的函数使用这两个局部变量,再调用返回函数时外部函数的局部变量依然保存在内存中。
这种闭包的调用方式看起来很古怪,为什么Kotlin中会有这种奇怪的语法呢?其实在kotlin中定义数据变量有两种作用域,一种是全局变量一种是局部变量。
var x = 100
fun hello() {
var y = 200
println(x)
println(y) // 没有问题
}
fun main(args: Array<String>) {
hello()
println(x)
// println(y) 编译器报错
}
从上面的例子可以看出x这个全局变量在hello和main任意的函数中都可以访问,但是y这个变量只是局限在hello函数中可以使用。x的作用域是全局作用域就是全局变量,而y的作用域也就只在hello函数范围内也就是局部变量。
如果开发者想要定义一个变量它只能被某些函数访问但是却有着全局变量的生命周期,考虑将变量定义成全局变量那么生命周期满足了但是这个变量能够被其他的函数访问,再考虑定义成局部变量那么函数返回后变量就消失了,这时候使用闭包就能够很好地解决这个问题。
函数复合
和数学里地符合函数概念一致,kotlin中如果某些函数经常联合起来使用,可以将它们符合在一起做成一个函数,用户只需要调用这一个函数就能实现复杂功能。
// 普通加8
var add8 = {
i: Int -> i + 8
}
// 普通乘积
var multiply = {
i: Int -> i * 3
}
// 符合函数是一个中缀操作符
infix fun <P1, P2, R> Function1<P1, P2>.andThen(function: Function1<P2, R>): Function1<P1, R> {
return fun(p1: P1): R {
return function.invoke(this.invoke(p1))
}
}
fun main(args: Array<String>) {
// 将两个函数复合起来
var add8andmultiply = add8 andThen multiply
println(add8andmultiply(3))
}
上面的复合函数其实是对Function1类型增加了扩展方法andThen,这个函数包括一个Function1类型的参数,最终先调用当前函数并且将其返回值作为参数调用传入的函数,这里使用闭包语法将这个调用做成了一个新的函数返回。
柯里化
将多源函数变成多个一元函数的链式调用,其实还是用到了闭包的语法,不断的返回新的函数直到将所有的参数都使用完。
// 原始版本
fun save(table: String, id: Int, name: String, age: Int) {
println("insert into $table values($id, '$name', $age)")
}
// 直接返回函数版本
fun save(table: String): (Int) -> (String) -> (Int) -> Unit {
return fun(id: Int): (String) -> (Int) -> Unit {
return fun(name: String): (Int) -> Unit {
return fun(age: Int) {
println("insert into $table values($id, '$name', $age)")
}
}
}
}
// 简写版本
fun save(table: String) = fun(id: Int) = fun(name: String) = fun(age: Int) =
println("insert into $table values($id, '$name', $age)")
fun main(args: Array<String>) {
// 原始调用方式
save("tb_user", 10, "zhangsan", 20)
// curry调用方式
save("tb_user")(10)("zhangsan")(20)
}
偏函数
传入部分参数到函数得到新的函数,为了实现这种功能前面的柯里化就有用武之地了。
fun save(table: String) = fun(id: Int) = fun(name: String) = fun(age: Int) =
println("insert into $table values($id, '$name', $age)")
val saveUser = save("tb_user")
saveUser(20)( "lishi")( 30)
在这个例子中定义一个保存到用户表的操作,也就是说第一个参数始终都是“tb_user”数据表,前面柯里化实现里直到返回的结果其实是一个闭包函数,可以直接使用saveUser这个偏函数。