1. inline
1.1 inline 函数的目的
- 减少方法的调用,进而减少虚拟机栈的出栈入栈操作
- 优化 Lambda 开销
1.1.1 优化 Lambda 开销
在Kotlin中每次声明一个Lambda表达式,就会在字节码中产生一个匿名类。该匿名类包含了一个invoke方法,作为Lambda的调用方法,每次调用的时候,还会创建一个新的对象。可想而知,Lambda虽然简洁,但是会增加额外的开销。Kotlin 采用内联函数来优化Lambda带来的额外开销。
不使用内联函数
1
2
3
4
5
6
7
8
9
fun main(){
testInline{
print(it)
}
}
private fun testInline(lam: (String) -> Unit) {
lam.invoke("lambda")
return
}
对标 Java 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public final void main() {
// 可以看到这里是生成了一个继承自 Function1 的类的单例
testInline(MainActivity$main$1.INSTANCE);
}
private final void testInline(Function1<? super String, Unit> lam) {
lam.invoke("lambda");
}
final class MainActivity$main$1 extends Lambda implements Function1<String, Unit> {
public static final MainActivity$main$1 INSTANCE = new MainActivity$main$1();
MainActivity$main$1() {
super(1);
}
public Object invoke(Object p1) {
invoke((String) p1);
return Unit.INSTANCE;
}
public final void invoke(String it) {
Intrinsics.checkNotNullParameter(it, "it");
System.out.print(it);
}
}
使用内联函数
1
2
3
4
5
6
7
8
9
10
fun main(){
testInline{
print(it)
}
}
private inline fun testInline(lam: (String) -> Unit) {
lam.invoke("lambda")
return
}
对标Java 代码
1
2
3
4
public final void main() {
String it = "lambda";
System.out.print(it);
}
1.2 inline 的实现原理
通过上面例子可以看出:inline 的工作原理就是将内联函数的函数体复制到调用处实现内联。
inline 的特性
- 内联函数无法递归
- 尽量避免对具有大量函数体的函数进行内联,这样会导致过多的字节码数量。
- public 内联函数无法调用私有成员变量,因为其他类调用内联函数最终是要 copy 到目标类中,而其他类是无法使用私有变量的
- 具体化参数类型
- 内联函数也会将其 lambda 参数内联
具体化参数类型
内联函数可以帮助我们实现具体化参数类型,Kotlin与Java一样,由于运行时的类型擦除,我们不能直接获取一个参数的类型。然而,由于内联函数会直接在字节码中生成相应的函数体实现,这时候反而可以获得参数的具体类型。使用 reified 修饰符来实现这一效果。
1
2
3
4
5
6
7
8
9
fun main() {
getType<Int>()
}
inline fun <reified T> getType() {
// 非内联函数是无法直接使用 T::class 的
println(T::class)
}
noinline
禁用内联,标记内联函数不需要内联的参数
我们的内联函数中有多个lambda 参数时,如果我们想要其中某一个lambda参数不进行内联,则可以使用noinline关键字。
1
2
3
4
5
6
7
8
9
10
11
12
fun main() {
myMethod(former = {
println("former")
}, latter = {
println("latter")
})
}
private inline fun myMethod(former: () -> Unit, noinline latter: () -> Unit) {
former()
latter()
}
在上述情况下 latter 会被编译成一个Function 而不是直接将实现代码 copy 到调用处
crossinline
标记内联函数的 lambda 表达式参数,标识该 lambda 函数返回为非局部返回,不允许非局部控制流
在使用inline函数时,有一个注意点:小心地使用流程跳转。示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun main() {
println("start execution:")
sayHello {
println("in lambda")
return
}
println("end execution")
}
private inline fun sayHello(block: () -> Unit) {
println("in sayHello")
block()
}
// 输出结果为:
// start execution:
// in sayHello
// in lambda
可以看出,最后的”end execution”并没有输出在控制台,那是因为程序在执行sayHello时,由于inline函数,导致return语句直接结束了main方法。我们如果想在sayHello定义时,就不想以后出现这样的使用隐患,就可以使用crossinline关键字:
1
2
3
4
private inline fun sayHello(crossinline block: () -> Unit) {
println("in sayHello")
block()
}
在加上了 crossinline 关键字之后,return 语句那块会显示语法错误。