Kotlin 之 inline & noline & crossinline
Posted 月盡天明
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin 之 inline & noline & crossinline相关的知识,希望对你有一定的参考价值。
inline & noline & crossinline
class TestInline
@JvmField
val TAG = "Test"
fun main()
Log.i(TAG, "main")
test1
Log.i(TAG, "test0")
private fun test1(test0: () -> Unit)
Log.i(TAG, "before test0")
test0()
Log.i(TAG, "after test0")
Log.i(TAG, "before test2")
test2()
Log.i(TAG, "after test2")
private fun test2()
Log.i(TAG, "test2")
根据上面的类作为一个 demo 来进行分析一下:
Decompile TestInline.kt 文件的 Bytecode 得到如下 Java 文件:
public final class TestInline
@JvmField
@NotNull
public final String TAG = "Test";
public final void main()
Log.i(this.TAG, "main");
this.test1((Function0)(new Function0()
// $FF: synthetic method
// $FF: bridge method
public Object invoke()
this.invoke();
return Unit.INSTANCE;
public final void invoke()
Log.i(TestInline.this.TAG, "test0");
));
private final void test1(Function0 test0)
Log.i(this.TAG, "before test0");
test0.invoke();
Log.i(this.TAG, "after test0");
Log.i(this.TAG, "before test2");
this.test2();
Log.i(this.TAG, "after test2");
private final void test2()
Log.i(this.TAG, "test2");
会发现
test0: () -> Unit
这个 lambda 表达式被转成了 Function0 这个接口。
Kotlin-stdlib-1.3.11-source.jar
kotlin.jvm.functions.Functions.kt
这个文件内部定义了 23 个 「Function」接口。
test1(test0: () -> Unit) // 这个函数的调用变成了对匿名内部类的函数回调
相当与lambda函数的调用会产生一些额外开销。
上面的 demo 还只是创建一个匿名内部类来使用,假如是多个呢?
fun main()
Log.i(TAG, "main")
intArrayOf(1, 2, 3, 4, 5).forEach
test1
Log.i(TAG, "test0 -- $it")
上面的例子 Decompile 之后如下:
public final void main()
Log.i(this.TAG, "main");
int[] $receiver$iv = new int[]1, 2, 3, 4, 5;
int[] var2 = $receiver$iv;
int var3 = $receiver$iv.length;
for(int var4 = 0; var4 < var3; ++var4)
int element$iv = var2[var4];
int var7 = false;
this.test1((Function0)(new TestInline$main$$inlined$forEach$lambda$1(element$iv, this)));
//
final class TestInline$main$$inlined$forEach$lambda$1 extends Lambda implements Function0
// $FF: synthetic field
final int $it;
// $FF: synthetic field
final TestInline this$0;
TestInline$main$$inlined$forEach$lambda$1(int var1, TestInline var2)
super(0);
this.$it = var1;
this.this$0 = var2;
// $FF: synthetic method
// $FF: bridge method
public Object invoke()
this.invoke();
return Unit.INSTANCE;
public final void invoke()
Log.i(this.this$0.TAG, "test0 -- " + this.$it);
假如对 lambda 表达式多次调用,则会产生多次匿名内部类,造成的内存开销就不可被忽视了。
inline
把 test(test0: ()-> Unit) 方法改造一下 ,加上 inline 关键字
fun main()
Log.i(TAG, "main")
test1
Log.i(TAG, "test0")
// inline
private inline fun test1(test0: () -> Unit)
Log.i(TAG, "before test0")
test0()
Log.i(TAG, "after test0")
Log.i(TAG, "before test2")
test2()
Log.i(TAG, "after test2")
Decompile 之后:
public final void main()
Log.i(this.TAG, "main");
int $i$f$test1 = false;
Log.i(this.TAG, "before test0");
int var3 = false;
Log.i(this.TAG, "test0");
Log.i(this.TAG, "after test0");
Log.i(this.TAG, "before test2");
access$test2(this);
Log.i(this.TAG, "after test2");
private final void test1(Function0 test0)
int $i$f$test1 = 0;
Log.i(this.TAG, "before test0");
test0.invoke();
Log.i(this.TAG, "after test0");
Log.i(this.TAG, "before test2");
access$test2(this);
Log.i(this.TAG, "after test2");
会发现,在 main 函数中包含了 test1 函数的整个方法体和lambda 表达式的内容。
Inline 的作用就是把被 inline 的方法提取到被调用处。
当出现 lambda 被调用多次的时候,也不会出现生成多个匿名内部类的问题。
fun main()
Log.i(TAG, "main")
intArrayOf(1, 2, 3, 4, 5).forEach
test1
Log.i(TAG, "test0")
private inline fun test1(test0: () -> Unit)
Log.i(TAG, "before test0")
test0()
Log.i(TAG, "after test0")
Log.i(TAG, "before test2")
test2()
Log.i(TAG, "after test2")
Decompile 如下:
public final void main()
Log.i(this.TAG, "main");
int[] $receiver$iv = new int[]1, 2, 3, 4, 5;
int[] var2 = $receiver$iv;
int var3 = $receiver$iv.length;
for(int var4 = 0; var4 < var3; ++var4)
int element$iv = var2[var4];
int var7 = false;
int $i$f$test1 = false;
Log.i(this.TAG, "before test0");
int var10 = false;
Log.i(this.TAG, "test0");
Log.i(this.TAG, "after test0");
Log.i(this.TAG, "before test2");
access$test2(this);
Log.i(this.TAG, "after test2");
// 省略 test1(Function0 test0)
即是有循环多次调用 test1(),也不会出现多个匿名内部类,而是把 test1() 的方法体整个包括循环调用。
return 处理
fun main()
Log.i(TAG, "main")
test1
Log.i(TAG, "test0 -- start")
return
Log.i(TAG, "test0 -- end")
假如在 lamdba 表达式中加上 return。
Decompile 之后:
public final void main()
Log.i(this.TAG, "main");
int $i$f$test1 = false;
Log.i(this.TAG, "before test0");
int var3 = false;
Log.i(this.TAG, "test0 -- start");
return 之后的 Log 语句根本就没有执行,并且 test1() 方法体,test0.invoke()之后的语句也没有执行。
那么怎么只 return lamdba 表达式的部分呢?
使用 return@xxx 「也就是带label 的 return」
fun main()
Log.i(TAG, "main")
test1
Log.i(TAG, "test0 -- start")
return@test1
Log.i(TAG, "test0 -- end")
Decompile之后如下:
public final void main()
Log.i(this.TAG, "main");
int $i$f$test1 = false;
Log.i(this.TAG, "before test0");
int var3 = false;
Log.i(this.TAG, "test0 -- start");
Log.i(this.TAG, "after test0");
Log.i(this.TAG, "before test2");
access$test2(this);
Log.i(this.TAG, "after test2");
此时会发现,只有 lamdba 表达式中 return 后面的语句没被执行,test1() 方法体的其他语句会正常执行。
noline
noline 是用来限制 lambda 表达式,强制它进行 inline 处理。
noline 必须配合 inline 使用,因为默认方法不进行 inline 处理,lamdba 表达式就是 noinline 的.
还是对上面的demo 进行修改如下:
fun main()
Log.i(TAG, "main")
test1
Log.i(TAG, "test0 -- start")
Log.i(TAG, "test0 -- end")
private inline fun test1(noinline test0: () -> Unit)
Log.i(TAG, "before test0")
test0()
Log.i(TAG, "after test0")
Log.i(TAG, "before test2")
test2()
Log.i(TAG, "after test2")
Decompile 如下:
public final void main()
Log.i(this.TAG, "main");
Function0 test0$iv = (Function0)(new Function0()
// $FF: synthetic method
// $FF: bridge method
public Object invoke()
this.invoke();
return Unit.INSTANCE;
public final void invoke()
Log.i(TestInline.this.TAG, "test0 -- start");
Log.i(TestInline.this.TAG, "test0 -- end");
);
int $i$f$test1 = false;
Log.i(this.TAG, "before test0");
test0$iv.invoke();
Log.i(this.TAG, "after test0");
Log.i(this.TAG, "before test2");
access$test2(this);
Log.i(this.TAG, "after test2");
会发现,除了 lamdba 表达式,test1()的方法体都被内敛。而 lamdba 表达式还是用内部类的方式进行实现。
另外,noinline 修饰的 lamdba 表达式不能使用 return 关键字。
但是可以使用 return@xxx
crossinline
加入想使用 inline 的特性,又不想在 lamdba 表达式中直接有 return 的情况出现,则可以使用 crossinline。
private inline fun test1(crossinline test0: () -> Unit)
// ...
编译器直接会提示不允许在此处使用 return
Decompile 之后如下:
public final void main()
Log.i(this.TAG, "main");
int $i$f$test1 = false;
Log.i(this.TAG, "before test0");
int var3 = false;
Log.i(this.TAG, "test0 -- start");
Log.i(this.TAG, "test0 -- end");
Log.i(this.TAG, "after test0");
Log.i(this.TAG, "before test2");
access$test2(this);
Log.i(this.TAG, "after test2");
完全不影响 inline 的效果。
But, return@xxx 是可以正常使用的。
break & continue
官网上有这么一句话:
breakandcontinueare not yet available in inlined lambdas, but we are planning to support them too.
break和continue在内联的 lambda 表达式中还不可用,但也计划支持它们。
记得很早前写了一篇文章关于 forEach 跳出循环的Kotlin 之 forEach 跳出循环 - crazy_jack - CSDN博客。
现在来看下 forEach 方法的定义:
public inline fun IntArray.forEach(action: (Int) -> Unit): Unit
for (element in this) action(element)
发现它是一个 inline 方法。这就会导致使用 return@xxx 的方式不能退出整个循环体。
所以想要跳出forEach 的循环体,继续往下执行,请参考这篇 blog 中的写法。
总结
inline 的好处:
- 减少方法调用产生的进栈和出栈操作,提升运行时的效率
- kotlin 的 inline 发生在编译时期
reference
- Inline Functions and Reified Type Parameters - Kotlin Programming Language
- Kotlin 中的 Lambda 与 Inline - 云+社区 - 腾讯云
以上是关于Kotlin 之 inline & noline & crossinline的主要内容,如果未能解决你的问题,请参考以下文章