8.高阶函数:Lambda作为形参和返回值
高阶函数,使用lambda作为参数或者返回值的函数。
val sum : (Int, Int) -> Int = {x,y -> x+y}
背后的原理是,函数类型被声明成普通的接口:一个函数类型的变量是FunctionN
接口的一个实现,kotlin标准库定义了一系列的接口,这些接口对应于不同参数数量的函数:Function0<R>
(没有参数的函数),Function1<P1, R>
(一个参数的函数)等等。每个接口定义了一个invoke方法,调用这个方法就会执行函数。一个函数类型的变量就是实现了对应的FunctionN
接口的实现类的实例,实现类的invoke
方法包含了lambda函数体。
Java 8的lambda会被自动转换成函数类型的值。
lambda会被正常地编译成匿名类,这表示没调用一次lambda表达式,一个额外的类就会被创建。并且如果lambda捕捉了某个变量,那么每次调用的时候都会创建一个新的对象。
使用inline
修饰符标记一个函数,在函数被使用的时候编译器并不会生成函数调用的代码,而是使用函数实现的真实代码替换每一次的函数调用。当一个函数被声明为inline
时,它的函数体是内联的——换句话说,函数体会被直接替换到函数调用的地方,而不是被正常调用。
不是所有使用lambda的函数都可以被内联。当函数被内联的时候,作为参数的lambda表达式的函数体会备直接替换到最终生成的代码中。这将限制函数体中的对应lambda参数的使用。如果lambda参数被调用,这样的代码能被容易地内联。但如果lambda的参数在某个地方被保存起来,以便后面继续使用,lambda表达式的代码将不能被内联,因为必须要有一个包含这些代码的对象存在。一般来说,参数如果被直接调用或者作为参数传递给另外一个inline函数,它是可以被内联的。否则编译器会禁止参数被内联并给出错误信息"Illegal usage of inline-parameter"(非法使用内联参数)
如果一个函数期望两个或更多lambda参数,可以选择只内联其中一些参数。接收这样的非内联lambda的参数,可以用noinline
修饰符来标记它。
inline fun foo(inlined:() -> Unit, noinline notInlined: () -> Unit) {...}
对于普通的函数调用,JVM已经提供了强大的内联支持。它会分析代码的执行,并在任何通过内联能够带来好处的时候将函数调用内联。这是在将字节码转换成机器码时自动完成的。在字节码中,每一个函数的实现只会出现一次,并不需要跟Kotlin的内联函数一样,每个调用的地方都拷贝一遍。再说,如果函数被直接调用,调用栈会更加清晰。
use
函数是一个扩展函数,被用来操作可关闭的资源,它接收一个lambda作为参数。这个方法调用lambda并且确保资源被关闭。无论lambda正常执行还是抛出了异常。当然,use函数是内联函数
只有在以lambda作为参数的函数是内联函数的时候才能从更外层的函数返回。在一个非内联函数的lambda中使用return表达式是不允许的。 在lambda中使用return关键字,它会从调用lambda的函数中返回,并不只是从lambda中返回,这样的return语句叫非局部返回,以为它从一个比包含return的代码块更大的代码块中返回了。在Java函数中,for循环或者synchronized代码块中使用return关键字,显示会从函数中返回,而不是从循环或者代码块中返回,当使用lambda作为参数的函数时kotlin保留了同样的行为。
局部返回
fun lookFoAlice(people : List<Person>) {
people.forEach label@ {
if(it.name == "Alice") return@label
}
println("Alice might be found")
}
fun lookFoAlice(people : List<Person>) {
people.forEach {
if(it.name == "Alice") return@forEach
}
println("Alice might be found")
}
println(StringBuilder().apply sb@ {
listOf(1,2,3).apply {
this@sb.append(this.toString())
}
})
匿名函数看起来和普通函数很相似,出了它的名字和参数类型被省略了之外。在匿名函数中,不带标签的return表达式会从匿名函数返回,而不是从包含匿名函数的函数返回,这条规则很简单:return从最近的使用fun关键字声明的函数返回。lambda表达式没有fun关键字,所以lambda中的return从最外层的函数返回,匿名函数使用了fun,因此,return表达式从匿名函数返回,而不是从最外层的函数返回。