简单理解 Kotlin 中的 inline 关键字
参考:
inline 这个词其实很好理解,翻译成中文就指内联。
相信每一个学习 Kotlin 的同学都有用过或者遇见过 inline
这个关键字。即便没有在 Kotlin 中见过那也不是什么问题,本文将带领你理解这个关键词。
实际上,如果你有学过C++,那你已经知道它是个什么玩意了。
本文就 Kotlin 中的 inline
进行讲解。
inline 是如何工作的
在此之前,我想先谈一谈 inline 与 lambda
Kotlin 在许多情况下使用编译器技巧来隐藏 Java 的旧语言结构。
在使用函数作为另一个函数的参数(所谓的高阶函数)比在Java中更自然。
虽然 Kotlin 好处多多,但在用 lambda 时有一些缺点。由于 lambda 表达式都会被悄悄的编译成一个匿名类。这也就意味着需要占用内存。如果短时间内 lambda 表达式被多次调用,大量的对象实例化就会产生内存流失(Memory Churn)。
所以为了避免这种情况,我们就可以使用 inline
inline fun inlined(getString: () -> String?) = println(getString())
fun notInlined(getString: () -> String?) = println(getString())
上面的代码中,一个是被 inline
修饰的内联函数,另一个不是。虽然他们的业务解决结果完全相同,都是打印getString()
,但如果你实际调用了这两个函数,从反编译后的Java代码中就可以看到很大的差异。
实际调用:
fun test() {
var testVar = "Test"
notInlined { testVar }
inlined { testVar }
}
相应的反编译的Java代码:
public static final void test() {
final ObjectRef testVar = new ObjectRef();
testVar.element = "Test Variable";
// notInlined:
notInlined((Function0)(new Function0(0) {
public Object invoke() {
return this.invoke();
}
@NotNull
public final String invoke() {
return (String)testVar.element;
}
}));
// inlined:
String var3 = (String)testVar.element;
System.out.println(var3);
从反编译的代码来看,我们并没有找到实际调用inlined
函数的迹象。
而 notInlined
使用 Function0
匿名类作为参数调用了该函数。为了遵守Function0
接口的协议所以就使用invoke()
实现了该方法。由于接口是通用的,因此生成另外的方法,即所谓的桥接方法。
通过上面的示例,你应该大概也看明白了
inline 的工作原理就是将内联函数的函数体复制到调用处实现内联。
inlined
函数内的(也就是 inlined
函数等号右边的) printlin()
,被拿去直接 System.out.println()
。
所以如果我们的方法比较大或者调用的比较多的话,那么编译器生成的代码量就会变大。
值得注意的是,如果方法变得足够大,过度使用 inline
可能会妨碍或停止 Hotspot 优化(例如方法内联)。默认情况下, Hotspot 不会内联大于35个字节的方法。
我们不应该内联所有功能。而且官方也不建议这样做。
在 kotlin-style-guide 这个库里的 issue 中找到个来自 Kotlin 贡献者的建议,
原文是:「Functions should only be made inline when they use inline-only features like inlined lambda parameters or reified types.」意思就是说:inline 关键字应该只用在需要内联特性的函数中,比如高阶函数作为参数和具体化的类型参数时。
当然了,官方也都是挺遵守这个规定的。比如在 Anko 库中,一个我很爱用的内联函数 bg
就有遵守着这个规定:
@PublishedApi
internal var POOL = newFixedThreadPoolContext(2 * Runtime.getRuntime().availableProcessors(), "bg")
inline fun <T> bg(crossinline block: () -> T): Deferred<T> = async(POOL) {
block()
}
要稍微注意一下的是 POOL
这个变量前面有 @PublishedApi
以及 internal
修饰着,这是因为内联函数体中不能直接访问到其外部类的成员,所以需要声明访问的成员为 internal
并且使用@PublishedApi
做注解。
相信看到这里,你已经对 inline 有了基本了解。