我正在参加「启航计划」

前语

在Kotlin中声明一个Lambda表达式,在编译字节码中会发生一个匿名类。此匿名类中有一个 invoke办法,为Lambda的调用办法,每次调用会创建一个新匿名类目标。可想而知,Lambda语法虽简洁,但额定添加的开支也不少。还有,若Lambda捕捉某个变量,每次调用时都会创建一个新目标,会导致效率较低。

在Kotlin中采纳优化Lambda额定开支的办法便是:内联函数。

回忆Java中采纳的优化办法:invokedynamic

invokedynamic技能是Java7后提出,在运行期才发生相应翻译代码。
invokedynamic被首次调用时,会触发发生一个匿名类来替换中间码invokedynamic,后续调用会直接采用该匿名类代码。这种做的好处主要有:

  • 详细的转化实现是在运行时发生,在字节码中只有一个固定的invokedynamic,所以需求静态生成的类的个数及字节码巨细都显著减少。
  • 与编译时写死在字节码中的战略不同,运用invokedynamic可把实际的翻译战略隐藏在JDK库的实现, 极大提高了灵活性,在确保向后兼容性的同时,后期可以持续对编译战略不断优化升级
  • JVM天然支撑针对该办法的Lambda表达式的翻译和优化,开发者在书写Lambda表达式时,可以彻底不用关心这个问题,极大地提升了开发体会。

Kotlin中采纳的优化办法:内联函数

Kotlin拥抱内联函数,在C++、C#等语言中也支撑这种特性。可以用 inline 关键字来修饰函数,这些函数就称为内联函数。它的函数体在编译期被嵌入到每一个被调用的当地,以减少额定生成的匿名类数,以及函数履行的时刻开支。

内联函数的作业原理并不杂乱,便是Kotlin编译器会将内联函数中的代码在编译的时分主动替换到调用它的当地,这样就不存在运行时的开支。

若想在用Kotlin开发时获得尽可能杰出的功能支撑,以及操控匿名类的生成数量,就来学习下内联函数:

如以下示例:

fun main() {
    foo {
        println("dive into Kotlin...")
    }
}
fun foo(block: () -> Unit) {
    println("before block")
    block()
    println("end block")
}

以上声明一个高阶函数foo,承受一个Lambda 参数为 () -> Unit,最终在main函数中调用它。下面是通过字节码反编译的Java代码:

public static final void main(@NotNull String[] args) {
    Intrinsics.checkParameterIsNotNull(args, "args");
    foo((Function0)null.INSTANCE);	        
}
public static final void foo(@NotNull Function0 block) {
    Intrinsics.checkParameterIsNotNull(block, "block");
    String var1 = "before block";
    System.out.println(var1);
    block.invoke();
    var1 = "end block";
    System.out.println(var1);
}

调用foo会发生一个Function()类型的block类,然后通过 invovke() 来履行,这样会添加额定生成类和调用开支。下面给foo函数加上inline修饰符:

inline fun foo(block: () -> Unit) {
    println("before block")
    block()
    println("end block")
}

看看相应Java代码:

public static final void main(@NotNull String[] args) {
    Intrinsics.checkParameterIsNotNull(args, "args");
    String va1 = "before block";
    System.out.println(var1);
    // block函数体在这里开始张贴
    String var2 = "dive into Kotlin...";
    System.out.println(var2);
    // block函数体在这里完毕张贴
    var1 = "end block";
    System.out.println(var1);
}
public static final void foo(@NotNull Function0 block) {
    Intrinsics.checkParameterIsNotNull(block, "block");
    String var2 = "before block";
    System.out.println(var2);
    block.invoke();
    var2 = "end block";
    System.out.println(var2);
}

如上面所说,foo 函数体代码及被调用的Lambda代码都张贴到了相应调用的方位。试想下,若是一个公共办法,或被嵌套在一个循环调用中,该办法势必会被调用很屡次。通过inline函数,可以消除这种额定调用,然后节约开支。

内联函数一个典型运用场景便是Kotlin调集类。Kotlin 中调集函数式API,如map、filter都是被界说成内联函数:

inline fun <T, R> Array<out T>.map {
    transform: (T) -> R
}: List<R>
inline fun <T> Array<out T>.filter {
    predicate: (T) -> Boolean
}: List<T>

很容易理解,因这些办法都接收Lambda表达式参数,需求对调集元素进行遍历操作,因而把相应的实现进行内联无疑是适合的。

但内联函数不是万能的,以下情况应防止运用内联函数:

  • JVM对普通函数已经可以根据实际情况智能判别是否进行内联优化,因而并不需求对其运用Kotlin的inline语法,否则只会让字节码变得愈加杂乱
  • 尽量防止对具有很多函数体的函数进行内联,会导致过多的字节码数量
  • 一个函数被界说为内联函数,就不能获取闭包类的私有成员,除非把它声明为internal