本文是对<<Kotlin in Action>>
的学习笔记,若是须要运行相应的代码能够访问在线环境 try.kotlinlang.org,这部分的思惟导图为: java
当咱们使用lambda
表达式时,它会被正常地编译成匿名类。这表示每调用一次lambda
表达式,一个额外的类就会被建立,而且若是lambda
捕捉了某个变量,那么每次调用的时候都会建立一个新的对象,这会带来运行时的额外开销,致使使用lambda
比使用一个直接执行相同代码的函数效率更低。函数
若是使用inline
修饰符标记一个函数,在函数被调用的时候编译器并不会生成函数调用的代码,而是 使用函数实现的真实代码替换每一次的函数调用。性能
当一个函数被声明为inline
时,它的函数体是内联的,也就是说,函数体会被直接替换到函数被调用地方,下面咱们来看一个简单的例子,下面是咱们定义的一个内联的函数:学习
inline fun inlineFunc(prefix : String, action : () -> Unit) {
println("call before $prefix")
action()
println("call after $prefix")
}
复制代码
咱们用以下的方法来使用这个内联函数:this
fun main(args: Array<String>) {
inlineFunc("inlineFunc") {
println("HaHa")
}
}
复制代码
运行结果为:spa
>> call before inlineFunc
>> HaHa
>> call after inlineFunc
复制代码
最终它会被编译成下面的字节码:code
fun main(args: Array<String>) {
println("call before inlineFunc")
println("HaHa")
println("call after inlineFunc")
}
复制代码
lambda
表达式和inlineFunc
的实现部分都被内联了,由lambda
生成的字节码成了函数调用者定义的一部分,而不是被包含在一个实现了函数接口的匿名类中。orm
在调用内联函数的时候,也能够传递函数类型的变量做为参数,仍是上面的例子,咱们换一种调用方式:cdn
fun main(args: Array<String>) {
val call : () -> Unit = { println("HaHa") }
inlineFunc("inlineFunc", call)
}
复制代码
那么此时最终被编译成的Java
字节码为:对象
fun main(args: Array<String>) {
println("call before inlineFunc ")
action()
println("call after inlineFunc")
}
复制代码
在这种状况,只有inlineFunc
的实现部分被内联了,而lambda
的代码在内联函数被调用点是不可用的。
若是在两个不一样的位置使用同一个内联函数,可是用的是不一样的lambda
,那么内联函数会在每个被调用的位置分别内联,内联函数的代码会被拷贝到使用它的两个不一样位置,并把不一样的lambda
替换到其中。
鉴于内联的运做方式,不是全部使用 lambda 的函数均可以被内联。当函数被内联的时候,做为参数的lambda
表达式的函数体会被 替换到最终生成的代码中。
这将限制函数体中的lambda
参数的使用:
lambda
参数 被调用,这样的代码能被容易地内联。lambda
参数 在某个地方被保存起来,以便之后继续使用,lambda
表达式的代码 将不能被内联,所以必需要 有一个包含这些代码的对象存在。通常来讲,参数若是 被直接调用或者做为参数传递 给另一个inline
函数,它是能够被内联的,不然,编译器会 禁止参数被内联 并给出错误信息Illeagal usage of inline-parameter
。
例如,许多做用于序列的函数会返回一些类的实例,这些类表明对应的序列操做并接收lambda
做为构造方法的参数,如下是Sequence.map
函数的定义:
fun <T, R> Sequence<T>.map(transform : (T) -> R) : Sequence<R> {
return TransformingSequence(this, transform);
}
复制代码
map
函数没有直接调用做为transform
参数传递进来的函数。而是将这个函数传递给一个类的构造方法,构造方法将它保存在一个属性当中。为了支持这一点,做为transform
参数传递的lambda
须要 被编译成标准的非内联表示法,即一个实现了函数接口的匿名类。
若是一个函数指望两个或更多的lambda
函数,能够选择只内联其中一些参数,由于一个lambda
可能会包含不少代码或者 以不容许内联的方式调用,接收这样的非内联lambda
的参数,能够用noinline
修饰符来标记它:
inline fun foo(inlined : () -> Unit, noinline noinlined : () -> Unit) {
}
复制代码
注意,编译器彻底支持 内联跨模块的函数或者第三方库定义的函数,也能够在 Java 中调用绝大部份内联函数。
大部分标准库中的集合函数都带有lambda
参数。例如filter
,它被声明为内联函数,这意味着filter
函数,以及传递给它的lambda
字节码会被内联到filter
被调用的地方,所以咱们不用担忧性能问题。
假如咱们像下面这样,连续调用filter
和map
两个操做:
println(people.filter{ it.age > 30 }.map(Person :: name))
复制代码
这个例子使用了一个lambda
表达式和一个成员引用,filter
和map
函数都被声明为inline
函数,因此不会额外产生类或者对象,可是上面的代码会建立一个中间集合来保存列表过滤的结果。
对于普通函数的调用,JVM
已经提供了强大的内联支持。它会分析代码的执行,并在任何经过内联可以带来好处的时候将函数调用内联。
带有lambda
参数的函数内联能带来好处:
lambda
建立匿名类,以及建立lambda
实例对象的开销。JVM
目前并无聪明到老是可以将函数调用内联。lambda
使用的特性,例如 非局部返回。可是在使用inline
关键字的时候,仍是应该注意代码的长度,若是你要内联的函数很大,将它的字节码拷贝到每个调用点将会极大地增长字节码的长度。在这种状况下,你应该将那些与lambda
参数无关的代码抽取到一个独立的非内联函数中。
当你使用lambda
去替换像循环这样的命令式代码结构时,很快就会遇到return
表达式的问题,把一个return
语句放在循环的中间是很简单的事。可是若是将循环替换成一个相似filter
的函数呢?
下面,咱们经过一个例子来演示,在集合当中寻找名为Alice
的人,找到了就直接返回:
data class Person(val name: String, val age: Int) val people = listOf(Person("Alice", 29), Person("Bob", 31))
fun lookForAlice(people: List<Person>) {
people.forEach {
if (it.name == "Alice") {
println("Found!")
return
}
}
println("Alice is not found")
}
fun main(args: Array<String>) {
lookForAlice(people)
}
复制代码
运行结果为:
>> Found !
复制代码
若是在lambda
中使用return
关键字,它会 从调用 lambda 的函数 中返回,并不仅是 从 lambda 中返回,这样的return
语句叫作 非局部返回,由于它从一个比包含return
的代码块更大的代码块中返回了。
须要注意的是,只有 以 lambda 做为参数的函数是内联函数 的时候才能从更外层的函数返回。在一个非内联的lambda
中使用return
表达式是不容许的,一个非内联函数能够把它的lambda
保存在变量中,以便在函数返回之后能够继续使用,这个时候lambda
想要去影响函数的返回已经太晚了。
也能够在lambda
表达式中使用局部返回,相似于for
循环中的break
表达式,它会终止lambda
的执行,并接着从调用lambda
的代码处执行。
要区分局部返回和非局部返回,要用到标签。想从一个lambda
表达式处返回你能够标记它,而后在return
关键字后面引用这个标签。
data class Person(val name: String, val age: Int) val people = listOf(Person("Alice", 29), Person("Bob", 31))
fun lookForAlice(people: List<Person>) {
people.forEach label@{
if (it.name == "Alice") return@label
}
println("Alice might be somewhere")
}
fun main(args: Array<String>) {
lookForAlice(people)
}
复制代码
运行结果为:
>> Alice might be somewhere
复制代码
另外一种选择是,使用lambda
做为参数的函数的函数名能够做为标签,也就是上面的forEach
,若是你显示地指定了lambda
表达式的标签,再使用函数名做为标签没有任何效果。
匿名函数是一种不一样的用于编写传递给函数的代码块的方式,先来看一个示例:
data class Person(val name: String, val age: Int) val people = listOf(Person("Alice", 29), Person("Bob", 31))
fun lookForAlice(people: List<Person>) {
people.forEach(fun (person) {
if (person.name == "Alice") return
println("${person.name} is not Alice")
})
}
fun main(args: Array<String>) {
lookForAlice(people)
}
复制代码
运行结果为:
>> Bob is not Alice
复制代码
匿名函数和普通函数有相同的指定返回值类型的规则,代码块匿名函数 须要显示地指定返回类型,若是使用 表达式函数体,就能够省略返回类型。
在匿名函数中,不带return
表达式会从匿名函数返回,而不是从包含匿名函数的函数返回,这条规则很简单:return
从最近的使用fun
关键字声明的函数返回。
lambda
表达式没有使用fun
关键字,因此lambda
中的return
从最外层的函数返回。fun
,所以return
表达式从匿名函数返回。尽管匿名函数看起来和普通函数很类似,但它实际上是lambda
表达式的另外一种语法形式而已。关于lambda
表达式如何实现,以及在内联函数中如何被内联的讨论一样适用于匿名函数。