我们在Kotlin中使用高级函数时,会带来一些运行效率上的损失:每一个函数都是一个对象,并且会捕获一个闭包。即那些在函数体内会访问到的变量。内存分配(对于函数对象和类)和虚拟调用会引入运行时间开销。
在编写代码的过程中,我们难免会遇见重复的代码,我们通常会抽取出一个公共方法,使得我们的代码看起来更简洁。
但是,如果这个方法被频繁调用,那么就会出现资源上的浪费。
例如:
//Kotlin
fun method(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
return operation(num1, num2)
}
fun main() {
val num1 = 10
val num2 = 20
val result = method(num1, num2) { n1, n2 ->
n1 + n2
}
print(result)
}
在反编译之后:
public static final void main() {
int num1 = 10;
int num2 = 20;
int result2 = _2_inlineKt.method(
(int)num1,
(int)num2,
(Function2 super Integer,? super Integer, Integer>)
((Function2)main.result.INSTANCE));
boolean bl = false;
System.out.print((int)result2);
}
我们可以看到,之前的Lambda表达式在这里变成了Function接口的匿名类实现,然后再invoke()中实现了运算逻辑,并将结果返回。
这就表明,我们每调用一次Lambda表达式,都会创建一个匿名类的实例,这就造成了额外的内存和性能开销。
为了解决这个问题,Kotlin提供了内联函数的功能以解决额外开销的问题。
//以Kotlin提供的内联函数forEach为例
val ints = intArrayof(0,1,2,3)
ints.forEach {
println("$it")
}
//forEach的实现
public inline fun IntArray.forEach(action: (Int) -> Unit): Unit {
for (element in this) action(element)
}
反编译之后:
public static final void main() {
int[] ints;
int[] $this$forEach$iv = ints = new int[]{0, 1, 2, 3};
boolean $i$f$forEach = false;
int[] arrn = $this$forEach$iv;
int n = arrn.length;
for (int i = 0; i < n; ++i) {
int element$iv;
int it = element$iv = arrn[i];
boolean bl = false;
boolean bl2 = false;
System.out.println((int)it);
}
}
编译器最终帮我们形成的是一个for循环。
内联函数的使用很简单,只需要在定义的高阶函数之前加上inline的关键字即可。
例如:
inline fun hello() {
print("Hello World")
}
高阶函数内联:
如果我们只希望内联一部分传给内联函数的Lambda表达式,只需在不想要被内联的参数之前加上noinline关键字即可。
inline testInline(block1: () -> Unit, noinline block2: () -> Unit) {
}
noinline的意义:
因为在内联函数编译时,会将内联的函数类型编译成写死的代码,没有真正的参数属性。
非内联的函数类型参数因为还是一个真实的参数,仍然可以自由的传递,而内联的函数类型参数只允许传递给另一个内联函数。
被noinline修饰的Lambda,编译器会使用匿名内部类的方法来处理Lambda。
有时,我们既想让 lambda 也被 inline,但是又不想让 lambda 对调用方的控制流程产生影响。这个产生影响,可以是有意识的主动控制,但是大多数情况下是开发人员的不小心导致的。我们知道 java 语言是一个编译型语言,如果能在编译期间对这种 inline lambda 对调用方产生控制流程影响的地方进行提示甚至报错,就万无一失了。
inline fun f(crossinline body: () -> Unit) {
val f = object: Runnable {
override fun run() = body()
}
}
fun printString(str: String, block: (String) -> Unit) {
println("printString begin")
block(str)
println("printString end")
}
fun main() {
println("main start")
val str = ""
printString(str) {
println("lambda start")
//在Lambda表达式中,不允许直接使用return关键字
//这里的return@printString 表示局部返回,将不再执行Lambda表达式中剩余的部分。
if (it.isEmpty()) return@printString
println(it)
println("lambda end")
}
println("main end")
}
局部返回运行结果:
main start
printString begin
lambda start
printString end
main end
如果我们将printString()函数声明称内联函数,我们就可以在Lambda表达式中使用return关键字。此时return将直接返回至外层的调用函数。
inline fun printString(str: String, block: (String) -> Unit) {
...
}
fun main() {
...
printString(str) {
println("lambda start")
//此时,可以使用return表达式
if (it.isEmpty()) return@printString
...
}
println("main end")
}
非局部返回运行结果:
main start
printString begin
lambda start
引用: