關於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不能拷貝函數方法到調用的地方執行的問題。

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