Kotlin学习笔记之 20 内联函数

247 阅读4分钟

首发于公众号: DSGtalk1989

20.内联函数

  • 关键字inline

    高阶函数需要传入的函数参数最终都会通过对象的方式去使用,而为了提升性能,我们需要使用inline关键字去修饰内联函数,内联函数可以直接将方法体编译至调用处。

    
    //kotlin
    fun notInlineFun(){
          val a = 1L
          val b = "123"
      }
      
      inline fun inlineFun(){
          val c = 2L
          val d = "456"
      }
      
      fun main() {
          notInlineFun()
          inlineFun()
      }
      
      
    //decompiled
    public final class InlineTestKt {
         public static final void notInlineFun() {
            long a = 1L;
            String b = "123";
         }
      
         public static final void inlineFun() {
            int $i$f$inlineFun = 0;
            long c = 2L;
            String d = "456";
         }
      
         public static final void main() {
            notInlineFun();
            int $i$f$inlineFun = false;
            long c$iv = 2L;
            String var3 = "456";
         }
      
         // $FF: synthetic method
         public static void main(String[] var0) {
            main();
         }
      }
    

    我们可以看到,inlineFun中的函数内容全部被编译复制到了main方法中。

  • 到底什么时候内联

    我们在上面的例子中,会发现kotlin给出了一个提示

    expected performance impact of inlining is insignificant. Inlining works best for functions with parameters of functional types

    直接这样使用inline对于性能的影响微乎其微,建议将inline和lambda表达式结合起来用,即官方建议我们将内联函数使用在高阶函数中。

    那么是否我们所有的高阶函数为了提高性能都可以直接使用inline来进行就修饰了呢,也不尽然。

    目前看来内联函数由于是代码拷贝的方式,本身提高性能的同时,可以进行代码内return

    首先我们来看一下,没有用inline形容的高阶函数。

    fun html( a : (String) -> Unit){
    		a.invoke("abc")
    		a("def")
    }
    

    如果我们想要跳出的话,需要加入标签才能跳出,并且只是单单针对函数体的返回类型Unit的函数,同时只能跳出函数体。

    html {
          return@html
    }
    

    而比如我们有这样一个,如果给到的String不是我们需要的那个,我们希望直接跳出包含调用函数的函数体,而不是单单跳出html函数,及如下的效果

    fun main(){
        html {
      	  if(it.startWith("a")){
      	     println("failed")
      	     //希望此处退出,并且连下面的success也不答应出来
      	  }
        }
        println("success")
    }
    

    这时候我们就不得不用内联函数来进行处理,由于内联函数是整个函数的拷贝进入,就是等于把整个lambda函数原封不动的拷贝到调用所在地。所以return就能跳出调用函数的函数体

    fun main(){
        html {
      	  if(it.startsWith("a")){
      	     println("failed")
      	     return
      	  }
        }
        println("success")
    }
    

    目前的kotlin版本中暂时不支持breakcontinue

  • crossinline和noinline

    在高阶函数中,我们用noinline关键字和crossinline来修饰lambda表达式,首先这两个表达式都不允许进行return,不一样的是noinline是彻底的不进行拷贝,而crossinline依然是拷贝的,只是不允许return

    使用inline修饰的高阶函数,默认参数函数都是直接内联拷贝的。

  • 实体化类型参数reified

    有时候我们只是单单需要使用一下类型,即java中的classType,比如说如果这个传参是什么类型的话,那我们就做不同的处理。一般情况下我们会这样处理。

    fun <T> doSomeThing(a : T) : Unit{
          if(a is String){
              println("is String")
          }
          println("is Other")
      }
      
      fun <T> doSomeThing(clazz : Class<T>) : Unit{
          if(clazz.isInstance(String)){
              println("is String")
          }
          println("is Other")
      }
    

    这种情况通常我们在调用的时候都会省去<>,因为系统都可以帮我们判断出来是什么类型。

    那么我们再来看一下这样一种情况,类A中有一个b属性,我们需要看下这个b是否是某个属性。

    class A{
          val b = Any()
      }
      
      
      fun <T> A.bIsType(clazz : Class<T>){
          if(clazz.isInstance(b)){
              println("get it")
          }
      }
      
      fun main() {
          val a = A()
          a.bIsType(String::class.java)
      }
    

    我们生成了一个A的扩展函数,传入一个类型参数,然后判断一下b是不是我们需要的那个类型。

    换一种方式,我们希望直接可以直接把泛型的类型拿来用,而不是需要传入具体的Class,这样我们就不需要往里面传入具体的对象了,直接通过<String>的方式,这里我们就需要使用到reified

    为什么要放在内联函数中介绍呢,因为reified关键字只会和inline一起出现,修改后为如下

    inline fun <reified T> A.bIsType(){
          if(b is T){
              println("get it")
          }
      }
      
      fun main() {
          val a = A()
          a.bIsType<String>()
      }
    

    这样一来,泛型T可以直接当成类型对象来使用,相当的方便。

  • 内联属性

    属性有getset的方法,如果这两个方法中并没有涉及到field的复杂运算,我们也可以将属性相应的用inline进行修饰,我们可以直接修饰属性,或者修饰属性的get或者set方法。

    val foo: Foo
        inline get() = Foo()
    
    var bar: Bar
        get() = ...
        inline set(v) { ... }
      
    inline var bar: Bar
        get() = ...
        set(v) { ... }
    

    具体在什么地方使用,暂时还没有参透。。