1、什么是内联函数
在前面的文章介绍的kotlin—匿名函数及其实现原理及kotlin—lambda及其原理等类高价函数,都会生成内部匿名类,调用的时候会创建匿名对象来调用对应的方法,这样的实现方式与普通函数相比性能上差了好多,如果只是为了使用上的方便而使用这种高阶函数,对追求高性能的应用中并没有什么吸引力。对于这种情况kotlin提供了内联函数来解决性能上的影响,内联函数就是为了消除高阶函数需要额外的内部匿名类和创建匿名类实例对象以及二次函数调用的性能损耗。
2、内联函数语法及使用
内联函数的语法如下:
inline fun 函数名([[noinline]函数参数列表]) [:返回类型]{
[函数体]
}
语法与普通函数的区别只是在函数的加个inline的标记,用于标记此函数数内联函数,函数参数可以在前面使用noinline进行修饰表示此函数类型不需要内联。
inline 修饰的函数使函数本身及其参数中的lambda参数都内联到使用处,noinline可指定lambda参数不需要内联到使用处。
使用举例如下:
inline fun inlineFun(a: Int, fun1: (Int) -> Int): Int {
return a + fun1(a)
}
3、内联函数原理
内联函数是为了消除高阶函数需要额外的内部匿名类和创建匿名类实例对象以及二次函数调用带来的性能损耗,实际上它是将调用内联函数的地方全部替换为内联函数体,我们通过举例来分析其原理:
举例:
class InlineFun {
val b = 20
inline fun lFun(obj: InlineFun, body: () -> Int) : Int {
return obj.b + body()
}
fun main() {
val a = lFun(InlineFun()) {
10
}
}
}
例子中InlineFun定义了一个内联函数lFun,它的参数分别是InlineFun类型和一个函数类型;main方法中调用内联函数lFun传递了InlineFun实例对象和一个lambda表达式,lFun返回InlineFun实例对象对成员b和lambda表达式函数10的和,所以main方法计算出的a = obj.b + body() = 20 + 10 = 30。
我们对InlineFun编译之后发现并没没有生成额外的内部匿名类,使用javac -c - v -p InlineFun.class来分析其原理:
public final class com.wyx.tcanvas.test.delegate.InlineFun
//省略......
{
//声明成员变量b
private final int b;
descriptor: I
flags: (0x0012) ACC_PRIVATE, ACC_FINAL
//构造函数
public com.wyx.tcanvas.test.delegate.InlineFun();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
//0-1: 调用父类的init方法即Object的init方法
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
//4-7:给成员变量b赋予初值20
4: aload_0
5: bipush 20
7: putfield #12 // Field b:I
10: return
//省略......
//取成员变量b的值的函数
public final int getB();
descriptor: ()I
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
Code:
stack=1, locals=1, args_size=1
//0-4: 返回b的值
0: aload_0
1: getfield #12 // Field b:I
4: ireturn
//省略......
//声明函数lFun,参数分别是InlineFun类型和Function0类型
public final int lFun(com.wyx.tcanvas.test.delegate.InlineFun, kotlin.jvm.functions.Function0<java.lang.Integer>);
descriptor: (Lcom/wyx/tcanvas/test/delegate/InlineFun;Lkotlin/jvm/functions/Function0;)I
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
Code:
stack=2, locals=4, args_size=3
//省略.....
//14-31:obj.getB() + body()
14: aload_1
15: invokevirtual #32 // Method getB:()I
18: aload_2
19: invokeinterface #38, 1 // InterfaceMethod kotlin/jvm/functions/Function0.invoke:()Ljava/lang/Object;
24: checkcast #40 // class java/lang/Number
27: invokevirtual #43 // Method java/lang/Number.intValue:()I
30: iadd
31: ireturn
//省略......
//声明main函数
public final void main();
descriptor: ()V
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
Code:
stack=2, locals=8, args_size=1
0: aload_0
1: astore_2
//2-6: 创建InlineFun实例对象,并调用其init方法
2: new #2 // class com/wyx/tcanvas/test/delegate/InlineFun
5: dup
6: invokespecial #47 // Method "<init>":()V
9: astore_3
10: iconst_0
11: istore 4
//13-31: 调用InlineFun实例对象的getB方法 加上10 并赋值给局部变量a,即语句:a = obj.getB() + 10
13: aload_3
14: invokevirtual #32 // Method getB:()I
17: istore 6
19: iconst_0
20: istore 5
22: bipush 10
24: istore 7
26: iload 6
28: iload 7
30: iadd
31: istore_1
32: return
//省略......
}
//省略.....
通过反编译可以看到调用内联函数的地方变成了内联函数的内部语句,如果内联函数中的函数类型使用了lambda表达式,则进一步内联lambda表达,将lambda表达式的函数体替换为调用lambda表达式的地方。所以在上述例子经过编译之后变成由:
val a = lFun(InlineFun()) {
10
}
替换变成:
val a = InlineFun().getB() + 10
4、总结
内联函数可以消除高阶函数需要额外的内部匿名类和创建匿名类实例对象以及二次函数调用带来的性能损耗,但是因为通过将内联函数的内部语句替换为调用语句的原理,所以会增加代码量,使用内联函数要避免内联函数的内部语句不要过大,因为会导致多个地方调用内联函数的地方都替换为内联函数的内部语句,导致代码量大量增多,导致软件/系统/app的整体大小。
可以将内联函数的原理理解为:内联函数的函数体语句是一个副本,经过编译之后会将内联函数的函数体语句拷贝解构并替换调用内联函数的语句。