美文网首页Kotlin编程KotlinKotlin精选
Kotlin边用边学:Inline Functions的适用场景

Kotlin边用边学:Inline Functions的适用场景

作者: 朱和 | 来源:发表于2018-07-06 08:33 被阅读1次
    kotlin+android-360.jpg

    划重点(Takeaway):

    • Collection自己提供的处理函数(forEach/map...)都支持inline,能用就别自己写循环
      • 在严格执行了上条后,你基本上就不怎么需要了解/写inline functions了
    • 升级第三方库后,务必重新编译你的项目
    • 自己写for/while循环,且循环的N值很大时,是使用inline function的一个切入点
    • 大函数不使用inline
    • 理解不了的时候看反编译生成的Java有奇效
    • 看源代码可以增进了解

    基本介绍

    Inline functions,中文大概就是内联/内嵌函数,字面的意思就是把内部(偷偷的)把被调用函数的代码连接(Copy)过来,具体看下代码和反编译的结果:

    源代码:

    fun main(args: Array<String>) {
        val localGreeting = "Hello from main"
    
        Demo().withPublicField { println(localGreeting) }
    }
    
    class Demo() {
        val title = "title in demo"
    
        inline fun withPublicField(otherFun: () -> Unit) {
            println("Call from withPublicField, title: $title")
            otherFun()
        }
    }
    

    查看反编译的Java:(IntelliJ IDEA/Android Studio Tools -> Kotlin -> Show Kotlin Bytecode,别忘了点击Decompile按钮)

    public final class MainKt {
       public static final void main(@NotNull String[] args) {
          final String localGreeting = "Hello";
          (new Demo()).withPublicField((Function0)(new Function0() {
             ...
             public final void invoke() {
                String var1 = localGreeting;
                System.out.println(var1);
             }
          }));
       }
    }
    
    public final class Demo {
       private final String title = "title_private";
    
       @NotNull
       public final String getTitle() { return this.title; }
    
       public final void withPublicField(@NotNull Function0 otherFun) {
          String var2 = "Call from withPublicField, title: " + this_$iv.getTitle();
          System.out.println(var2);
          otherFun.invoke();
       }
    }
    

    由于Java的function不是first-class member,所以其高阶函数(higher-order function)的使用,实际上存在着封装function成对应的wrapper class的实例,并对可见范围内(closure)的变量存在引用/访问。这实际上是以一定的资源损耗换来的便利性。对于我们上述的代码:

    • 第4行(Function0)(new Function0() {...}即是function转化成wrapper class实例

    • 第7行 String var1 = localGreeting;即是对可见范围变量的引用

    在平常使用中,这个损耗是微小可忽略的,但如果是被一个N次(N值很大)循环中调用,那积少成多的损耗就是一个值得重视的问题了。

    对于这个问题,Kotlin的处理方式是直接在编译期将原来的higher-order function的执行/实现代码,copy到调用处。具体看代码(唯一的改动点:原函数前添加了inline关键字):

        inline fun withPublicField(otherFun: () -> Unit) {
            ...
        }
    

    然后查看生成的Java二进制:

        public final class MainKt {
           public static final void main(@NotNull String[] args) {
              String localGreeting = "Hello";
              Demo this_$iv = new Demo();
              String var3 = "Call from withPublicField, title: " + this_$iv.getTitle();
              System.out.println(var3);
              System.out.println(localGreeting);
           }
        }
    

    注意对比两次生成的java代码:原先的L4~L10,现在的L4~L7。可以看到,新的Java代码很简单粗暴的把withPublicField的实现代码copy进入了main函数(自然移除了对withPublicField的调用)。

    好处

    • 不需要额外创建wrapper class instance
    • 不需要维护可见范围内的变量引用/调用

    代价肯定有的,不然就所有的方法都设成inline不就完了:-)

    使用注意点

    任何inline function的修改,必须重新编译后才生效

    这个从之前反编译的代码可以推断出,毕竟调用inline function的代码已经被抹除,替换成了inline function的实现代码。重新编译才能再次重复这个替换过程。这个对于调用第三方库的inline函数尤其需要注意。

    private变量

    之前我们看到植入的代码中有这么一行代码(L5):

    String var3 = "Call from withPublicField, publicTitle: " + this_$iv.getPublicTitle();
    

    这里,getPublicTitle()如果是个private会怎样,相信大家可以猜测到。具体可以用代码验证:

    class Demo() {
        private val title = "title in demo"
        ...
    }
    

    修改成private可见性后,代码编译出错(this_$iv.getPublicTitle()在main函数是不可见了)

    当然inline函数的写法,除了这个变量的可见性还有其他,譬如:Non-local returns,但只要理解了其编译时的copy行为后,很多就水落石出了。(好的IDE其实还是给了很多有用的提示的,所以尽管写,让IDE去操这个心吧)

    源代码查看/验证

    Iterator.forEach

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

    可以看到,自带的Collection的函数都早已支持了inline了。所以,能使用自带的Collection函数就别写自己的循环了。

    Bonus

    相关文章

      网友评论

        本文标题:Kotlin边用边学:Inline Functions的适用场景

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