Funciones en línea de Kotlin en línea, no en línea, en línea cruzada

Funciones de orden superior

Primero observe la definición de funciones de orden superior en kotlin: si una función recibe otra función como parámetro, o el tipo de retorno es un tipo de función, entonces la función se denomina función de orden superior .

Por ejemplo el siguiente código:

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

Entre ellos highFucestá el nombre de la función, la función pasa 2 parámetros, el primer parámetro es el Stringtipo, el segundo parámetro es el tipo de función, la parte a la izquierda de -> se usa para declarar qué parámetros recibe la función, y entre múltiples parámetros Sepárelos con comas, si no hay parámetros, puede usar () directamente, el lado derecho de -> indica qué tipo de valor de retorno es la función, si no hay valor de retorno, puede usarlo directamente Unit.

función en línea

Una función en línea, como sugiere el nombre, consiste en asignar directamente el cuerpo de la función como un parámetro de función a la llamada de función en tiempo de compilación, usando directamente un ejemplo para ilustrar:

fun requestInfo() {
    getStr()
}

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

Muy simple, se imprime una cadena en getStr(), y luego se llama a la función getStr() en requestInfo(), después de convertir el código anterior en código Java:

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

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

Continúe y agregue una declaración en línea antes de getStr(), de la siguiente manera:

fun requestInfo() {
    getStr()
}

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

Después de convertir a Java:

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

Se puede ver que existen diferencias obvias en el código después de la conversión a Java: después de agregar en línea, el contenido de la función en getStr() se "copia y pega" directamente en requestInfo(), es decir, en línea con la llamada a la función .

en línea

A través del ejemplo anterior, el papel de en línea es obvio, es decir, el contenido de la función se copia y pega directamente en el lugar de llamada en el momento de la compilación.

Sabemos que la llamada de función finalmente se completa a través del marco de pila de la pila de operandos de JVM. El proceso de cada método desde el inicio de la llamada hasta la finalización de la ejecución corresponde al proceso de un marco de pila de pila a pila. en la pila de la máquina virtual Use La palabra clave en línea teóricamente puede reducir el nivel de un marco de pila.

Entonces, ¿es adecuado agregar la palabra clave en línea delante de todas las funciones? La respuesta es no. De hecho, la propia JVM admite funciones en línea al compilar, lo cual no es exclusivo de kotlin. Entonces, ¿qué tipo de funciones en kotlin necesitan usar la palabra clave en línea? Respuesta: ¡Funciones de orden superior!

只有高阶函数中才需要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,包治百病的性能良药?

Supongo que te gusta

Origin juejin.im/post/7084838324422049822
Recomendado
Clasificación