关于Kotlin,你不知道的那些事(一)------inline,noinline,crossinline

1.inline

相信大家很多在写kotlin代码的时候都看到过这样的关键字,比如常见的let,with,apply,also,但是大家又是否知道代表了什么含义呢,加了inline的方法能调用,不加inline关键字的方法也能调用,那这个关键字到底有什么作用呢?接下来我们通过实例来分析一下。

我们写一个测试类来分析。

fun main(args: Array<String>) {

    val result = sum(1, 2)

    print("result = $result")


}

fun sum(numA: Int, numB: Int): Int {
    return numA + numB
}

假如我们有这样1个方法,这个方法是没有添加inline关键字的,输出结果为:3,我们先查看一下最后编译为的java代码是怎么样的

 public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      int result = sum(1, 2);
      String var2 = "result = " + result;
      boolean var3 = false;
      System.out.print(var2);
   }

   public static final int sum(int numA, int numB) {
      return numA + numB;
   }

从上面的代码可以看到,sum方法变成了一个static的方法,在main方法中第三行调用了这个sum方法,最后得到结果并输出。接下来我们再来看看加上了inline的方法。

fun main(args: Array<String>) {

    val result = sumInline(1, 2)

    print("result = $result")


}

inline fun sumInline(numA: Int, numB: Int): Int {
    return numA + numB
}

调用完以上方法以后,最后输出结果也是 3, 我们在看看编译后的java代码

public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      byte numA$iv = 1;
      int numB$iv = 2;
      int $i$f$sumInline = false;
      int result = numA$iv + numB$iv;
      String var5 = "result = " + result;
      boolean var6 = false;
      System.out.print(var5);
   }


   public static final int sumInline(int numA, int numB) {
      int $i$f$sumInline = 0;
      return numA + numB;
   }

从上面的代码我们可以看到,在main方法中,没有调用 sumInline 这个方法的地方,反而是在倒数第四行的地方,自己执行了2个数相加的代码逻辑,最后输出了结果。

从这里我们差不多可以得出一个

1.2 结论:

在调用了使用inline关键字声明的方法的时候,会在调用的地方,把那个方法里面的代码逻辑,拷贝到调用的地方执行,
不再通过方法(函数)的调用实现。

大家看懂上面的结论了吗? 大概意思就是,在调用使用了inline声明的方法的时候,最后生成的代码显示出你并不是在在调用这个方法,而是直接在你调用的地方执行方法里面的代码逻辑,这样做的一个好处就是

减少了方法(函数)调用的开销,特别是在循环调用的时候。

1.3 注意

inline 内联函数只适合函数内方法比较小,逻辑简单的情况下调用,如果你的逻辑复杂,建议还是不要使用内联函数。

2.noinline

根据名称可以看到,no-inline,好像是说不内联,从上面我们所知道的内联函数就是把函数里面的逻辑拷贝到调用函数的地方,那么no-inline意思是不是就是不拷贝到调用函数的地方呢? 我们还是以上面的代码为例,写一个noinline的例子

fun main(args: Array<String>) {

    sumNoinline(1, 2) { result ->
        print("result = $result")
    }


}


inline fun sumNoinline(numA: Int, numB: Int, noinline callback: (result: Int) -> Unit) {
    val result = numA + numB
    callback(result)
}

注意,这里noinline和inline的使用必须是配合使用,noinline 关键字需要声明在kotlin的函数方法上。接下来我们看看编译后的java代码是怎么样的。

public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      byte numA$iv = 1;
      byte numB$iv = 2;
      Function1 callback$iv = (Function1)null.INSTANCE;
      int $i$f$sumNoinline = false;
      int result$iv = numA$iv + numB$iv;
      callback$iv.invoke(result$iv);
   }

   public static final void sumNoinline(int numA, int numB, @NotNull Function1 callback) {
      int $i$f$sumNoinline = 0;
      Intrinsics.checkParameterIsNotNull(callback, "callback");
      int result = numA + numB;
      callback.invoke(result);
   }

可以看到上面的代码,出现了一个叫Function1的对象,我们找到这个Fnuction1的具体代码

/** A function that takes 0 arguments. */
public interface Function0<out R> : Function<R> {
    /** Invokes the function. */
    public operator fun invoke(): R
}
/** A function that takes 1 argument. */
public interface Function1<in P1, out R> : Function<R> {
    /** Invokes the function with the specified argument. */
    public operator fun invoke(p1: P1): R
}
/** A function that takes 2 arguments. */
public interface Function2<in P1, in P2, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2): R
}

...............更多省略.....................

上面这样方法一共到Function22,表示可以最多的一个接口可以接收22个参数,我们这里因为只有一个参数在callback里面,所以,上面生成的java代码是Function1,在上面的java代码的main方法里面可以看到,里面也没有出现调用sumNoinline这个方法,相同的是,sumNoinline方法里面的2个数相加的逻辑也被放到了main方法中去实现了,这是不是也意味着noinline也把代码拷贝到了调用的地方执行呢,但是这里有一个需要注意的地方就是用noinline声明的这个callback函数方法参数,在main方法里面我们是这样实现的

 sumNoinline(1, 2) { result ->
    print("result = $result")
}

通过后面通过一个lambda表达式去实现的,相当于是把这个lambda传递给了callback,当然这里的执行就是funcation的invoke方法了。当然上面的代码我们还可以这样写

sumNoinline(1, 2,{result ->
    print(result)
})

但是系统还是推荐我们用第一种写法,从上面的代码我们可以看到,我们lambda里面的一句输出的话在这里是看不到的,说明我们lambda里面的代码被放到了function中去执行了。接下来我们看另一个例子,这是上一个inline没有演示的

fun main(args: Array<String>) {
    suminline(1,2){result->
        println("result = $result")
    }
}


inline fun suminline(numA: Int, numB: Int,callback: (result: Int) -> Unit) {
    val result = numA + numB
    callback(result)

}

可以看到这个这个方法和最上面的suminline不同的的是多了一个callback的函数方法,我们对比下这个没有加noinline的函数方法和加了noinline的函数方法的区别

   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      byte numA$iv = 1;
      int numB$iv = 2;
      int $i$f$suminline = false;
      int result$iv = numA$iv + numB$iv;
      int var6 = false;
      String var7 = "result = " + result$iv;
      boolean var8 = false;
      System.out.println(var7);
   }

   public static final void suminline(int numA, int numB, @NotNull Function1 callback) {
      int $i$f$suminline = 0;
      Intrinsics.checkParameterIsNotNull(callback, "callback");
      int result = numA + numB;
      callback.invoke(result);
   }

看到区别了吗 哥哥们 ???

如果没看明白,下面我就给大家一点点讲解一下。大家记得去对比下看是不是这样哦。

2.2 结论:

1.在inline内联函数上的普通函数方法(没有加入noinline),在最后生成的java代码,会把函数方法里实现的代码
(lambda里面的代码)也给copy到调用的地方。
(上面的代码可以看到最后输出的一句话就是本来在lambda表达式里面,
最后生 成的java代码里面lambda里面的代码被拷贝到了调用的地方)

2.在使用了noinline的函数方法,lambda里面的方法是不会被拷贝到调用的地方执行的,会创建一个Function的接口类,
来实现lambda里面的方法,

那么我们应该在什么情况下使用noinline呢? 看下面这样一个例子

fun main(args: Array<String>) {
    suminline(1, 2) { result ->
        println("result = $result")
    }
}


inline fun suminline(numA: Int, numB: Int,  callback: (result: Int) -> Unit) {
    val result = numA + numB
    addNumber(callback)
}

fun addNumber(callback: (result: Int) -> Unit) {
    callback.invoke(1)
}

在这里插入图片描述

他会建议我们添加一个noinline关键字去修饰这个callback,这样就能达到复用的目的了。这里的addNumber只是一个普通的方法,如果addNumber也是一个inline内联方法,也可以不用添加,noinline关键字。

所以这里再次声明一下

2.3 注意:

1.inline会增加代码量,虽然你看不见,但是编译后的代码会有所增加,因为他把实现都拷贝到了调用函数的地方,
特别建议如果函数过大,不要使用inline关键字去声明函数,建议如果函数里面只有两三行代码的时候去使用,在某些情况,循环遍历的时候调用使用了inline的方法会提升效率。

2.如果你想让你的callback复用,那么可以添加上noinline关键字,让他不内联。

3.inline函数是允许在lambda中使用return的,如果添加了noinline关键字的lambda表达式是不能直接return,必须指定return的函数位置。
(这个意思就是,因为inline是拷贝了lambda里面的方法的,所以在return的时候没有问题,如果是在使用了noinline的lambda里面return,
因为这里面的方法是在Function里面执行的,并不能应用return到外部的返回,只能return到调用的这个function 不能白可以留言给大家再解释哈。)

3.crossinline

首先我们要知道的是,crossinline和noinline都是需要配合inline一起使用的,只有声明了inline的内联函数才能够使用noinline和crossinline关键字声明使用函数方法。

接下来我们看看crossinline和noinline有什么不同。

fun main(args: Array<String>) {
    suminline(1, 2) { result ->
        println("result = $result")
    }
}


inline fun suminline(numA: Int, numB: Int, noinline callback: (result: Int) -> Unit) {
    val f = Runnable { callback(numA + numB) }
    f.run()
}

这是使用了noinline的代码,看看编译后的java代码是怎么样,这里我们相当于是在Runnable的lambda表达式里面调用了callback的lambda表达式

public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, “args”);
byte numAiv=1;bytenumBiv = 1; byte numBiv = 2;
Function1 callback$iv = (Function1)null.INSTANCE;
int iifsuminline=false;Runnablefsuminline = false; Runnable fiv = (Runnable)(new Runnable(callbackiv,numAiv, numAiv, numB$iv) {
// $FF: synthetic field
final Function1 $callback;
// $FF: synthetic field
final int $numA;
// $FF: synthetic field
final int $numB;

     public final void run() {
        this.$callback.invoke(this.$numA + this.$numB);
     }

     public {
        this.$callback = var1;
        this.$numA = var2;
        this.$numB = var3;
     }
  });
  f$iv.run();

}

public static final void suminline(int numA, int numB, @NotNull Function1 callback) {
int iif$suminline = 0;
Intrinsics.checkParameterIsNotNull(callback, “callback”);
Runnable f = (Runnable)(new Runnable(callback, numA, numB) {
// $FF: synthetic field
final Function1 $callback;
// $FF: synthetic field
final int $numA;
// $FF: synthetic field
final int $numB;

     public final void run() {
        this.$callback.invoke(this.$numA + this.$numB);
     }

     public {
        this.$callback = var1;
        this.$numA = var2;
        this.$numB = var3;
     }
  });
  f.run();

}
}

可以看到,他把这个方法内容拷贝到了调用的地方,callback这个lambda表达式的内容放置到了Function中去执行了。这里看不到

再来看看使用了crossonline的例子

fun main(args: Array<String>) {
    suminline(1, 2) { result ->
        println("result = $result")
    }
}


inline fun suminline(numA: Int, numB: Int, crossinline callback: (result: Int) -> Unit) {
    val f = Runnable { callback(numA + numB) }
    f.run()
}

只是替换了一下把noinline替换为crossinline

public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      byte numA$iv = 1;
      int numB$iv = 2;
      int $i$f$suminline = false;
      Runnable f$iv = (Runnable)(new Unit_AKt$main$$inlined$suminline$1(numA$iv, numB$iv));
      f$iv.run();
   }

   public static final void suminline(final int numA, final int numB, @NotNull final Function1 callback) {
      int $i$f$suminline = 0;
      Intrinsics.checkParameterIsNotNull(callback, "callback");
      Runnable f = (Runnable)(new Runnable() {
         public final void run() {
            callback.invoke(numA + numB);
         }
      });
      f.run();
   }
}
// Unit_AKt$main$$inlined$suminline$1.java
package com.kotlin;

import kotlin.Metadata;

@Metadata(
   mv = {1, 1, 16},
   bv = {1, 0, 3},
   k = 3,
   d1 = {"\u0000\n\n\u0000\n\u0002\u0010\u0002\n\u0002\b\u0002\u0010\u0000\u001a\u00020\u0001H\n¢\u0006\u0002\b\u0002¨\u0006\u0003"},
   d2 = {"<anonymous>", "", "run", "com/kotlin/Unit_AKt$suminline$f$1"}
)
public final class Unit_AKt$main$$inlined$suminline$1 implements Runnable {
   // $FF: synthetic field
   final int $numA;
   // $FF: synthetic field
   final int $numB;

   public Unit_AKt$main$$inlined$suminline$1(int var1, int var2) {
      this.$numA = var1;
      this.$numB = var2;
   }

   public final void run() {
      int result = this.$numA + this.$numB;
      int var2 = false;
      String var3 = "result = " + result;
      boolean var4 = false;
      System.out.println(var3);
   }
}

这次生成的代码,比使用了noinline的时候多得多,我们来分析下哪些不一样。

在使用了crossinline的时候,它会把lamada里面的方法拷贝到执行的地方,不再生成一个Function来执行。在这个代码,因为内部有一个Runable的内部类,所以这里重新创建了一个Runable的实现类,可以看到在Runable这个实现类的run方法里面,lambda里面的代码被拷贝到了这里执行。

4.总结

所以看完了上面的分析,大家明白了各自的用途了吗。

1.inline的作用是会把调用的方法里面的代码拷贝到调用的地方执行。
2.noline需要配合inline一起使用,noline声明的lambda函数里面得代码不会被拷贝到代码执行的地方。会创建一个Funcation来实现lambda里面得方法。
3.crossinlne 如果内部是内部的lambda,那么最后回调地方的lambda表达式中的代码也会被拷贝到内部类的实现的地方调用

那么我们应该怎么使用crossinlne和noline呢,

1.如果你想让你的lambda表达式传递到给其他lambda使用,比如上面的callback这个函数方法传递给Runable使用,那么这个时候你就可以选择使用crossinlne或noinline

2.如果你想让你的lambda里面的方法拷贝到调用它的上一个lambda表达式中使用,那么使用crossinlne,否则使用noinline

3.注意这一个重要的功能就是:要想lambda传递使用的时候 使用crossinlne或noinline。

5.完

我觉得,总的来说呢,Kotlin给我们提供内联函数的作用就是代码优化的作用,可以提升程序执行效率。到底是调用函数执行,还是把函数体拷贝到调用的地方执行。crossinlne和noinline的作用就是让lambda表达式可以传递给下一个方法的函数使用,crossinlne解决了noinline不能拷贝函数方法到调用的地方执行的问题。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章