Kotlin的inline内联

76 阅读4分钟

内联函数 的语义很简单: 把函数体复制粘贴到函数调用处 。使用起来也毫无困难,用 inline关键字修饰函数即可。

那么他的存在是为了干什么呢?

1.inline内联的本质

内联就是把函数体复制粘贴到函数调用处 , 顾名思义,就是把函数里面的代码直接放到调用方,直接上代码

  inline fun testA() {
        println("I'm a inline")
    }
    fun run() { testA() }

转存失败,建议直接上传图片文件

反编译之后

 public final void testA() {
      int $i$f$testA = 0;
      String var2 = "I'm a inline";
      System.out.println(var2);
   }

   public final void run() {
      int $i$f$testA = false;
      String var3 = "I'm a inline";
      System.out.println(var3);
   }

转存失败,建议直接上传图片文件

可以看到,run函数没有调用testA函数,而是直接调用的testA里面的代码,那这样有什么作用呢?

这就涉及到了JVM的方法机制

JVM 进行方法调用和方法执行依赖 栈帧,每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程

线程的栈帧是存储在虚拟机栈中,未内联的情况下,当我们调用run函数时就会产生一个栈帧,在run中在调用testA就会产生两个栈帧。

使用内联之后就会减少调用方法的成本,只产生一个栈帧。

这么方便!那为什么很少有人用呢?

这是因为在java中已经使用了内联,只不过是放到虚拟机里做的,防止开发者的滥用,比如我内联了一个很长的方法,极大的增大字节码长度,反而得不偿失,所以交给虚拟机就行了。

2.kotlin的内联

kotlin中使用内联主要是为了Lambda 使用

    fun main() {
        println("main")
        normal {
            println("normal")
            return
        }
        println("main end")
    }

    inline fun normal(block:() -> Unit) {
        block()
    }

// 输出
    main
    normal
  • 对于普通的函数,使用内联函数是完全没有必要的,只是减少了一次方法栈的调用,这种优化可以忽略

  • 对于带有函数类型参数的高阶函数,我们使用inline关键字修饰的内联函数,来节省Lambda表达式在调用的地方创建匿名类带来的内存开销

  • 由于内联函数,不仅可以内联自己内部的代码,还可以内联内部的函数体中的代码(Lambda表达式中的代码)。在调用的地方仅仅只是代码的替换,我们可以在Lambda表达式中,直接使用裸return来完成最外层函数的返回。这种返回(位于 lambda 表达式中,但退出包含它的函数)我们称之为非局部返回。

  • 如果需要内联的函数代码逻辑过于复杂,调用该函数又比较频繁,则会导致在编译期间调用该内联函数的地方出现代码臃肿的情况。

3.noinline

noinline 关键字用于标记不应该内联的 lambda 参数。默认情况下,内联函数的所有 lambda 参数都会被内联展开,但有时我们可能希望某些 lambda 参数不被内联。 作用

  • 防止内联:阻止特定的 lambda 参数被内联展开。
  • 保留 lambda 参数:适用于需要将 lambda 参数作为对象传递的情况。

既然inline是一种优化,假设使用者也经过考虑,将函数用inline修饰,那为什么还会有noinline这个关键字? 先来思考一个问题:kotlin 中一切都是对象,函数也能作为参数或者返回值,那被内联的函数参数作为参数或者返回值时会怎么样? 答案是不可以,因为被内联的函数已经被展开了,不再是一个对象了,那怎么办?加上noinline,告诉编译器,这个函数参数不要进行内联。 这里也有一个例外情况,被内联的函数参数,可以作为其他内联函数的参数。为啥?因为被内联函数被展开复制到调用处了哇

inline fun hello(preAction:()->Unit, postAction:()->Unit):()->Unit{
    preAction()
    println("hello")
    postAction()
    another(postAction)
    /**
     * Illegal usage of inline-parameter 'postAction' in 'public inline fun hello(preAction: () -> Unit, postAction: () -> Unit): () -> Unit defined in root package in file Inline.kt'. Add 'noinline' modifier to the parameter declaration
     */
    anotherInline(postAction)
    return postAction
    /**
     * Illegal usage of inline-parameter 'postAction' in 'public inline fun hello(preAction: () -> Unit, postAction: () -> Unit): () -> Unit defined in root package in file Inline.kt'. Add 'noinline' modifier to the parameter declaration
     */
}

fun another(action:()->Unit){
    action()
}
inline fun anotherInline(action:()->Unit){
    action()
}

这里调用another(postAction)return postAction时,IDE 会报错,提示需要加上noinline。 也就是说,如果 inline 函数参数中有函数对象,并且这个函数对象需还需要充当其他非 inline 函数的参数或者充当返回值,那么就需要加上noinline,还有个偷懒的办法,IDE告诉你需要加,那就加上。

作者:Huang兄
链接:juejin.cn/post/738524…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

转载自作者:Huang兄 链接:juejin.cn/post/738524…