Kotlin:内联函数

Kotlin:内联函数

  • 为什么要使用内联函数
  • 内联函数的使用
  • noinline和crossinline
    • noinline
    • crossinline
  • 内联高阶函数的return

为什么要使用内联函数

我们在Kotlin中使用高级函数时,会带来一些运行效率上的损失:每一个函数都是一个对象,并且会捕获一个闭包。即那些在函数体内会访问到的变量。内存分配(对于函数对象和类)和虚拟调用会引入运行时间开销。

在编写代码的过程中,我们难免会遇见重复的代码,我们通常会抽取出一个公共方法,使得我们的代码看起来更简洁。

但是,如果这个方法被频繁调用,那么就会出现资源上的浪费。

例如:

//Kotlin
fun method(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
    return operation(num1, num2)
}

fun main() {
    val num1 = 10
    val num2 = 20
    val result = method(num1, num2) { n1, n2 ->
        n1 + n2
    }
    print(result)
}

在反编译之后:

public static final void main() {
        int num1 = 10;
        int num2 = 20;
        int result2 = _2_inlineKt.method(
            (int)num1,
            (int)num2,
            (Function2)
            ((Function2)main.result.INSTANCE));
        boolean bl = false;
        System.out.print((int)result2);
    }

我们可以看到,之前的Lambda表达式在这里变成了Function接口的匿名类实现,然后再invoke()中实现了运算逻辑,并将结果返回。

这就表明,我们每调用一次Lambda表达式,都会创建一个匿名类的实例,这就造成了额外的内存和性能开销。

为了解决这个问题,Kotlin提供了内联函数的功能以解决额外开销的问题。

//以Kotlin提供的内联函数forEach为例
val ints = intArrayof(0,1,2,3)
ints.forEach {
    println("$it")
}

//forEach的实现
public inline fun IntArray.forEach(action: (Int) -> Unit): Unit {
    for (element in this) action(element)
}

反编译之后:

public static final void main() {
        int[] ints;
        int[] $this$forEach$iv = ints = new int[]{0, 1, 2, 3};
        boolean $i$f$forEach = false;
        int[] arrn = $this$forEach$iv;
        int n = arrn.length;
        for (int i = 0; i < n; ++i) {
            int element$iv;
            int it = element$iv = arrn[i];
            boolean bl = false;
            boolean bl2 = false;
            System.out.println((int)it);
        }
    }

编译器最终帮我们形成的是一个for循环。

内联函数的使用

内联函数的使用很简单,只需要在定义的高阶函数之前加上inline的关键字即可。

例如:

inline fun hello() {
    print("Hello World")
}

高阶函数内联:

  1. 函数本身被内联到调用处;
  2. 函数的函数参数被内联到调用处。

noinline和crossinline

noinline

如果我们只希望内联一部分传给内联函数的Lambda表达式,只需在不想要被内联的参数之前加上noinline关键字即可。

inline testInline(block1: () -> Unit, noinline block2: () -> Unit) {
}

noinline的意义:

因为在内联函数编译时,会将内联的函数类型编译成写死的代码,没有真正的参数属性。

非内联的函数类型参数因为还是一个真实的参数,仍然可以自由的传递,而内联的函数类型参数只允许传递给另一个内联函数。

被noinline修饰的Lambda,编译器会使用匿名内部类的方法来处理Lambda。

crossinline

有时,我们既想让 lambda 也被 inline,但是又不想让 lambda 对调用方的控制流程产生影响。这个产生影响,可以是有意识的主动控制,但是大多数情况下是开发人员的不小心导致的。我们知道 java 语言是一个编译型语言,如果能在编译期间对这种 inline lambda 对调用方产生控制流程影响的地方进行提示甚至报错,就万无一失了。

inline fun f(crossinline body: () -> Unit) {
    val f = object: Runnable {
        override fun run() = body()
    }
}

内联高阶函数的return

fun printString(str: String, block: (String) -> Unit) {
    println("printString begin")
    block(str)
    println("printString end")
}

fun main() {
    println("main start")
    val str = ""
    printString(str) {
        println("lambda start")
        //在Lambda表达式中,不允许直接使用return关键字
        //这里的return@printString 表示局部返回,将不再执行Lambda表达式中剩余的部分。
        if (it.isEmpty()) return@printString
        println(it)
        println("lambda end")
    }
    println("main end")
}

局部返回运行结果:

main start
printString begin
lambda start
printString end
main end

如果我们将printString()函数声明称内联函数,我们就可以在Lambda表达式中使用return关键字。此时return将直接返回至外层的调用函数。

inline fun printString(str: String, block: (String) -> Unit) {
    ...
}

fun main() {
    ...
    printString(str) {
        println("lambda start")
        //此时,可以使用return表达式
        if (it.isEmpty()) return@printString
        ...
    }
    println("main end")
}

非局部返回运行结果:

main start
printString begin
lambda start

引用:

  1. https://kotlinlang.org/docs/reference/inline-functions.html

你可能感兴趣的:(Kotlin学习笔记)