Kotlinインライン関数inline、noinline、crossinline

高階関数

まず、kotlinの高階関数の定義を見てください。関数が別の関数をパラメーターとして受け取る場合、または戻り型が関数型である場合、その関数は高階関数と呼ばれます

たとえば、次のコード:

private fun highFuc(name: String, block: (String) -> Unit) {
    block(name)
}
复制代码

その中にhighFucは関数名があり、関数は2つのパラメーターを渡します。最初のパラメーターはStringタイプ、2番目のパラメーターは関数タイプ、->の左側の部分は、関数が受け取るパラメーターを宣言するために使用されます。パラメーターパラメーターをコンマで区切ります。パラメーターがない場合は()を直接使用できます。->の右側は関数の戻り値のタイプを示し、戻り値がない場合は直接使用できますUnit

インライン関数

インライン関数は、その名前が示すように、関数本体を関数パラメーターとしてコンパイル時に関数呼び出しに直接マップすることです。例を使用して、次のことを直接説明します。

fun requestInfo() {
    getStr()
}

fun getStr() {
    println("inline")
}
复制代码

非常に単純です。文字列がgetStr()で出力され、上記のコードをJavaコードに変換した後、getStr()関数がrequestInfo()で呼び出されます。

public final void requestInfo() {
   this.getStr();
}

public final void getStr() {
   String var1 = "inline";
   System.out.println(var1);
}
复制代码

次のように、getStr()の前にインラインステートメントを追加します。

fun requestInfo() {
    getStr()
}

//普通函数中并不推荐加inline关键字
inline fun getStr() {
    println("inline")
}
复制代码

Javaに変換した後:

public final void requestInfo() {
   String var3 = "inline";
   System.out.println(var3);
}
复制代码

javaに変換した後のコードには明らかな違いがあることがわかります。インラインを追加した後、getStr()の関数コンテンツはrequestInfo()に直接「コピーペースト」されます。つまり、関数呼び出しにインラインになります

列をなして

上記の例から、インラインの役割は明らかです。つまり、関数のコンテンツはコンパイル時に呼び出し元に直接コピーされて貼り付けられます。

関数呼び出しは、JVMオペランドスタックのスタックフレームを介して最終的に完了することがわかっています。呼び出しの開始から実行の完了までの各メソッドのプロセスは、スタックからスタックへのスタックフレームのプロセスに対応します。仮想マシンスタックで使用します。inlineキーワードを使用すると、理論的にはスタックフレームレベルを下げることができます。

では、すべての関数の前にインラインキーワードを追加するのは適切ですか?答えはノーです。実際、JVM自体はコンパイル時に関数のインライン化をサポートしていますが、これはkotlinに固有のものではありません。では、kotlinのどのような関数でinlineキーワードを使用する必要がありますか?回答:高階関数!

只有高阶函数中才需要inline去做内联优化,普通函数并不需要,如果在普通函数强行加上inline,编辑器会立刻提醒:

Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types
复制代码

意思是 内联对普通函数性能优化的预期影响是微不足道的。内联最适合带有函数类型参数的函数

为什么高阶函数要使用inline

inline优化了什么问题呢?因为我们使用的Lambda表示式在编译转换后被换成了匿名类的实现方式。

fun requestInfo() {
    highFuc("inline") { str ->
        println(str)
    }
}

fun highFuc(name: String, block: (String) -> Unit) {
    block(name)
}
复制代码

转换成java之后:

public final void requestInfo() {
   this.highFuc("inline", (Function1)null.INSTANCE);
}

private final void highFuc(String name, Function1 block) {
   block.invoke(name);
}

public interface Function1<in P1, out R> : Function<R> {
    public operator fun invoke(p1: P1): R
}
复制代码

所以函数参数最终会转换成interface,并通过创建一个匿名实例来实现。这样就会造成额外的内存开销。为了解决这个问题,kotlin引入inline内联功能,将Lambda表达式带来的性能开销消除。还是上面的例子,这次我们对高阶函数添加inline关键字:

fun requestInfo() {
    highFuc("inline") { str ->
        println(str)
    }
}

//注意:这里添加了inline关键字
inline fun highFuc(name: String, block: (String) -> Unit) {
    block(name)
}
复制代码

转换成java之后:

public final void requestInfo() {
   String name$iv = "inline";
   System.out.println(name$iv);
}
复制代码

noinline

当函数被inline标记时,使用noinline可以使函数参数不被内联。

fun requestInfo() {
    highFuc({
        println("noinline")
    }, {
        println("inline")
    })
}

//highFuc被inline修饰,而函数参数block0()使用了noinline修饰
inline fun highFuc(noinline block0: () -> Unit, block1: () -> Unit) {
    block0()
    block1()
}
复制代码

转换成java之后:

public final void requestInfo() {
   Function0 block0$iv = (Function0)null.INSTANCE;
   block0$iv.invoke();
   
   String var5 = "inline";
   System.out.println(var5);
}
复制代码

结果也很明显,block0()函数没有被内联,而block()函数被内联,这就是noinline的作用。

如果想在非内联函数Lambda中直接return怎么办?比如我想这么写:

fun requestInfo() {
    highFuc {
        return //错误,不允许在非内联函数中直接return
    }
}

fun highFuc(block: () -> Unit) {
    println("before")
    block()
    println("after")
}
复制代码

对不起,不允许!会直接在return的地方报**'return' is not allowed here**错误。 但是可以写成return@highFuc,即:

fun requestInfo() {
    highFuc {
        return@highFuc //正确,局部返回
    }
}

fun highFuc(block: () -> Unit) {
    println("before")
    block()
    println("after")
}
复制代码

其中return是全局返回,会影响Lambda之后的执行流程;而return@highFuc是局部返回,不会影响Lambda之后的执行流程。如果我就想全局返回,那么可以通过inline来进行声明:

fun requestInfo() {
    highFuc {
        return 
    }
}

inline fun highFuc(block: () -> Unit) {
    println("before")
    block()
    println("after")
}
复制代码

因为highFuc通过inline声明为内联函数,所以调用方可以直接使用return进行全局返回,执行requestInfo()的结果:

before
复制代码

可以看到Lambda之后的after并没有被执行,因为是全局返回,当然可以改成return@highFuc局部返回,这样就可以都执行了。

结论:内联函数所引用的Lambda表达式中是可以使用return关键字来进行函数返回的,而非内联函数只能进行局部返回

现在有一种场景,我既想使用inline优化高阶函数,同时又不想调用方打断我的执行流程(因为inline是支持全局return的),貌似冲突了,这时候怎么办呢,这时候就需要crossinline了。

crossinline

允许inline内联函数里的函数类型参数可以被间接调用,但是不能在Lambda表达式中使用全局return返回。

fun requestInfo() {
    highFuc {
        return //错误,虽然是inline内联函数,但Lambda中使用crossinline修饰,所以不允许全局返回了
    }
}

inline fun highFuc(crossinline block: () -> Unit) {
    println("before")
    block()
    println("after")
}
复制代码

crossinline关键字就像一个契约,它用于保证内联函数的Lambda表达式中一定不会使用return全局返回,这样就不会冲突了。当然return@highFuc局部返回还是可以的。

总结

  • inline:编译时直接将函数内容直接复制粘贴到调用处。
  • noinline:当函数被inline标记时,使用noinline可以使函数参数不被内联。
  • crossinline: 允许内联函数里的函数类型参数可以被间接调用,但是不能在Lambda表达式中使用全局return返回

参考

【1】高阶函数与 lambda 表达式 【2】juejin.cn/post/686995… 【3】重学 Kotlin —— inline,包治百病的性能良药?

おすすめ

転載: juejin.im/post/7084838324422049822