本文主要介绍kotlin的inline关键字。

inline

在kotlin中使用高阶方法确实很方便,但是这种便利是有代价的。我们知道 lambda 表达式其实就是一个 Function 对象。

例如:

fun nonInlined(block: () -> Unit) {
println("before")
block()
println("after")
}

对应Java代码为

public void nonInlined(Function block) {
System.out.println("before");
block.invoke();
System.out.println("after");
}

在kotlin中调用时

nonInlined {
println("do something here")
}

会被编译为下面的代码(简化)

nonInlined(new Function() {
@Override
public void invoke() {
System.out.println("do something here");
}
});

这里每次调用nonInlined()都会创建一个Function对象。如果这里调用的是非常通用的高阶函数,那么每次调用都会带来额外的内存、运行开销。

inline 就是 kotlin 是用来优化这个问题的关键字。

inline fun inlined(block: () -> Unit) {
println("before")
block()
println("after")
}

调用和之前一样

inlined {
println("do something here")
}

对应编译后的代码

System.out.println("before");
System.out.println("do something here");
System.out.println("after");

使用inline可以增加运行效率,降低内存占用。相应的,带来的副作用就是代码膨胀。所以,inline 的最佳使用场景是一些短小的高阶函数,或者需要在循环中调用的函数。

在 kotlin std 库中定义的高阶函数基本上都是被inline 修饰的,我们自己在写库时也可以使用这个优化点。

但是,被inline的函数有一些副作用。

1、被inline的函数,无法在函数内部访问private变量、方法(除非用internal@PublishedApi修饰)。

private val aPrivateMemberVariable = "I'm private"

@PublishedApi
internal val anInternalMemberVariable = "I'm internal"

val aPublicMemberVariable = "I'm public"

inline fun higherOrderFunction(aLambda: () -> Unit) {

//ERROR: can't access this variable in an inline function.
aPrivateMemberVariable.length

//Can access this variable because it's marked with @PublishedApi and internal
anInternalMemberVariable.length

//Can access this variable, it's public
aPublicMemberVariable.length

}

2、在被inline修饰的Function闭包内运行的lambda 中,可以使用 return 退出 calling 函数。

这种特性也被称为non-local control flow
这样做其实是为了让我们可以在forEach中的lambda有能力控制循环。

fun hasZeros(ints: List<Int>): Boolean {
ints.forEach {
if (it == 0) return true // returns from hasZeros
}
return false
}

但是,截止到1.1.4,kotlin 还不支持在non-local control flow中使用breakcontinue。😂

这个特性其实很容易产生bug,因为这样做的话,相当于剥夺了高阶函数对lambda运行的控制。比如在涉及资源处理时,乱用return很容易跳过了资源释放语句,而且不易调试。

而正常的lambda是没有能力return执行函数的,只能通过 return@label 的方式跳出 lambda自身。

inline fun higherOrderFunction(aLambda: () -> Unit) {

aLambda()

print("I won't be executed when you call callingFunction()")

}

fun callingFunction() {

higherOrderFunction {

print("Non-local control flow")

return

}

}

除了inline,kotlin中还有noinlinecrossinline等关键字。

noinline顾名思义,和inline含义相反。用于标记某个lambda,表示这个lambda在inline Function内不内联。

crossinline用的比较少,主要用于在嵌套的Function内部使用lambda,被crossinline标记的lambda不能在lambda内部使用return。

inline fun higherOrderFunction(crossinline aLambda: () -> Unit) {

normalFunction {
aLambda() //must mark aLambda as crossinline to use in this context.
}

}

fun normalFunction(aLambda: () -> Unit) {

return

}

fun callingFunction() {

higherOrderFunction {

return //Error. Can't return from here.
}

}