美文网首页Android开发Android开发经验谈
Kotlin中的inline关键字 - 内联函数

Kotlin中的inline关键字 - 内联函数

作者: 盛世光阴 | 来源:发表于2021-05-31 12:09 被阅读0次

    版权归作者所有,转发请注明出处:https://www.jianshu.com/p/14a837e67dd7

    Kotlin中的inline关键字 - 内联函数
    Kotlin中的inline关键字 - 内联类

    前言

    Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,被称之为 Android 世界的Swift,在Google I/O 2017中,Google 宣布 Kotlin 成为 Android 官方开发语言

    img.jpg

    内联函数

    inline关键字所修饰的函数为内联函数

    内联函数的作用

    尝试去修饰普通函数

    inline fun getAmount() = 0.1
    

    这种写法编辑器会有警告提示:
    Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types
    意味着使用inline只有在修饰一个参数为functional types的函数时候效果最好,也就是我们所说的高阶函数的一种,kotlin中的forEach函数中参数action就是一个函数类型,所以forEach函数使用了inline 修饰

    @kotlin.internal.HidesMembers
    public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
        for (element in this) action(element)
    }
    

    inline修饰高阶函数的好处
    我们对高阶函数分别使用普通函数声明,和使用内联函数声明

    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.checkParameterIsNotNull(f, "f");
          f.invoke();
       }
    
       public final void testInline(@NotNull Function0 f) {
          int $i$f$testInline = 0;
          Intrinsics.checkParameterIsNotNull(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函数的调用

    this.test((Function0)null.INSTANCE);
    

    call()中的其他代码可以看出,内联函数则是复制了函数体过来,而没有创建匿名类,而是直接嵌入了Lambda函数的实现体

    int $i$f$testInline = false;
    int var3 = false;
    String var4 = "testInline";
    boolean var5 = false;
     System.out.print(var4);
    

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

    使用 Inline 修饰高阶函数是会有性能上的提升

    避免内联大型函数

    在调用内联函数时,基于上述我们已经知道内联会将Lambda参数函数体直接进行嵌入,避免了函数的引用以及栈帧和对象创建的开销,但是如果Lambda所内联的函数体数量太大,嵌入则会造成调用位置的函数体增长,所以需要 避免内联大型函数

    noinline关键字使用

    内联Lambad只能在内联函数内部使用,或者作为内联参数进行传递,如果想 将内联Lambad作为普通函数参数进行传递或者存储在字段中则不能使用内联函数声明,或者在声明为内联函数然后将需要传递的Lambda参数指定为noinline

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

    如果一个函数中有多个函数参数,并且部分参数时可以直接使用内联方式调用,另一部分则需要进行传递或者赋值,则可以将函数声明为内联函数,内联函数中,不符合内联方式的参数使用noinline修饰

    inline fun testInline(f1: () -> Unit, noinline f: () -> Unit) {
        f1()
        testInlineInner(f)
    }
    
    fun testInlineInner(f: () -> Unit) {
        f()
    }
    

    内联Lambda中使用return

    • 在调用普通高阶函数时,在Lambda中不能直接使用retrun退出lambda表达式,需要进行声明然后退出lambda,inline lambda可以直接return,但是退出的是当前外部函数而不是lambda表达式
    fun call() {
        testOrdinary {
            print("testOrdinary")
            return //报错
        }
        testOrdinary {
            print("testOrdinary")
            return@testOrdinary //正常 退出 testOrdinary 
        }
        testInline {
            print("testInline")
            return //正常 退出call()
        }
        testInline {
            print("testInline")
            return@testInline //正常 退出 testInline 
        }
    }
    

    crossinline关键字使用

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

    inline fun testInline(crossinline callBack: () -> Unit) {
        object : Thread(){
            override fun run() {
                callBack() //执行在object中,需要将callBack声明为 crossinline
            }
        }
    }
    

    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 properties

    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(); //非嵌入
       }
    }
    

    欢迎关注Mike的简书

    Android 知识整理

    相关文章

      网友评论

        本文标题:Kotlin中的inline关键字 - 内联函数

        本文链接:https://www.haomeiwen.com/subject/vmqvsltx.html