欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Kotlin 中 Lambda的使用 四、内联函数 inline

程序员文章站 2022-03-15 13:37:18
...

一、内联函数

        用关键词inline修饰的函数,称为内联函数。inline修饰的是用函数作为参数的高阶函数,如果修饰普通函数为警告  Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types,普通函数内联对性能的影响微不足道。

二、用inline修饰高阶函数的好处

        我们在kotlin中对高阶函数分别使用普通函数声明和使用内联函数声明然后转成java代码然后进行比较。kotlin代码:

class Test {
    //不使用inline 修饰的高阶函数
    fun test(f: () -> Unit) {
        f()
    }
    //使用inline 修饰的高阶函数
    inline fun testInline(f: () -> Unit) {
        f()
    }
    fun call() {
        test { print("test") }
        testInline { print("testInline") }
    }
}

java代码:

public final class Test {
   public final void test(@NotNull Function0 f) {
      Intrinsics.checkNotNullParameter(f, "f");
      f.invoke();
   }

   public final void testInline(@NotNull Function0 f) {
      int $i$f$testInline = 0;
      Intrinsics.checkNotNullParameter(f, "f");
      f.invoke();
   }

   public final void call() {
      this.test((Function0)null.INSTANCE);
      int $i$f$testInline = false;
      int var3 = false;
      String var4 = "testInline";
      boolean var5 = false;
      System.out.print(var4);
   }
}

        在call中可以看出在调用非内联函数时是直接调用了此函数,并且创建了匿名类Function0用于Lambda函数的调用。内联函数则是复制了函数体过来,而没有创建匿名类,而是直接嵌入了Lambda函数的实现体。

        当高阶函数没有使用Inline修饰时,调用此函数会直接引用此函数,并且会创建匿名类以实现此函数参数的调用,这有两部分开销,直接调用此函数会创建额外的栈帧以及入栈出栈操作(一个函数的调用就是一个栈帧入栈和出栈的过程),并且匿名类的创建也会消耗性能。使用 Inline 修饰高阶函数是会有性能上的提升

         在调用内联函数时,基于上述我们已经知道内联会将Lambda参数函数体直接进行嵌入,内联可能导致生成的代码增加;不过如果我们使用得当(即避免内联过大函数),性能上会有所提升,尤其是在循环中的“超多态(megamorphic)”调用处。

三、禁用内联 noinline

        如果希望只内联一部分传给内联函数的 lambda 表达式参数,那么可以用 noinline 修饰符标记不希望内联的函数参数:

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { //…… }

        可以内联的 lambda 表达式只能在内联函数内部调用或者作为可内联函数的参数传递。当作为非内联函数的参数传递或者存储到字段中的时候需要noinline修饰。

inline fun testInline(f1: () -> Unit,  f: () -> Unit) {
    f1.invoke()
    val a = f//会提示错误,内联Lambad 不能参数赋值
    testInlineInner(f) //会提示错误,内联Lambad 不能作为普通函数参数进行传递
}

fun testInlineInner(f: () -> Unit) {
    f()
}
inline fun testInline(f1: () -> Unit,  noinline f: () -> Unit) {
    f1.invoke()
    val a = f//可以
    testInlineInner(f) //可以
}

四、在内联函数中的lambda表达式中使用return(非局部返回)

        在 Kotlin 中,我们只能对具名或匿名函数使用正常的、非限定的 return 来退出。 退出一个 lambda 表达式,我们必须使用一个标签, lambda 表达式内部禁止使用裸 return,因为 lambda 表达式不能使包含它的函数返回。

fun testInlineInner(f: () -> Unit) {
    f()
}
testInlineInner {
        return//'return' is not allowed here
    }

但是如果 lambda 表达式传给的函数是内联的,该 return 也可以内联,所以它是允许的 ,这种返回(位于 lambda 表达式中,但退出包含它的函数)称为非局部返回

inline fun testInlineInner(f: () -> Unit) {
    f()
}

fun main(args: Array<String>) {
    testInlineInner {
        return// 可以,退出main函数
    }
}

五、crossinline

        当内联Lambda不是直接在函数体中调用,而是在嵌套函数或者其他执行环境中调用则需要声明为crossinline。

inline fun f(crossinline body: () -> Unit) {
   val f = Runnable { body() }
}

六、Reified 类型的参数

        reified 是Kotlin关于泛型的关键字,从而达到泛型不被擦除,如果需要使用reified 去修饰泛型方法中的泛型类型,则需要使用Inline修饰此泛型方法,因为Inline函数可以指定泛型类型不被擦除,因为内联函数编译期会将字节码嵌入到调用它的地方,所以编译器才会知道泛型对应的具体类型,使用reified 和 Inline适用于泛型方法,并且方法体中需要对泛型类型做以判断的情况。​​​​​​​

inline fun <reified T> Bundle.plus(key: String, value: T) {
    when (value) {
        is String -> putString(key, value)
        is Long -> putLong(key, value)
        is Int -> putInt(key, value)
    }
}

七、内联属性

inline也可以修饰setget方法,并且可以直接修饰属性使两个访问器都标记为inline。

class Amount(var amount: Long) {
    private val isEmpty: Boolean
        inline get() {
            return amount <= 0
        }

    private val isEmptyNoInline: Boolean
        get() {
            return amount <= 0
        }

    fun test(){
       val amount = Amount(10)
        val isEmpty = amount.isEmpty
        val isEmptyNoInline = amount.isEmptyNoInline
    }
    //转化为Java代码
    public final void test() { 
      Amount amount = new Amount(10L);
      int $i$f$isEmpty = false;
      boolean isEmpty = amount.getAmount() <= 0L; //代码嵌入
      boolean isEmptyNoInline = amount.isEmptyNoInline(); //非嵌入
   }
}