前语

在前两篇文章中咱们介绍到Kotlin中的函数、高阶函数和Lambda表达式。这篇文章咱们来讲解Kotlin源码中常见的三个关键字inline、noinline、crossinline的运用,当然这是在把握了前两篇文章的基础上来打开介绍的。如果对Kotlin中的函数、高阶函数和Lambda表达式不熟悉的读者,能够看下这两篇文章。下面咱们开始本篇文章的学习。

1.部分回来

在介绍inline关键字之前,咱们有必要先来介绍下Kotlin中部分回来的概念。那么什么是部分回来呢? 在Kotlin中非内联的Lambda表达式是不支撑运用裸return的,在非内联的Lambda表达式内部咱们只能运用标签约束的return来进行部分回来。如果咱们强行在一个Lambda表达式中运用裸return,编译器会报语法错误。如下示例代码,咱们在main函数中调用高阶函数normal的时分运用裸return:

Kotlin语法基础篇五:inline、noinline、crossinline
而当咱们运用@标签约束的return,编译器不再提示语法错误。

Kotlin语法基础篇五:inline、noinline、crossinline
通常这种写法,咱们称为回来到标签。当一个lambda表达式在调用的时分,通常都会有一个默许的隐式标签,而这个隐式标签通常都是按照它外部的函数名来命名的。如上示例代码,咱们在调用高阶函数normal的时分,在lambda表达式内部运用return@normal来完结包裹它的外层函数normal的回来。当然咱们也能够自己界说标签的名称。

fun main() {
    normal test@{
        println("called normal")
        return@test
    }
}
fun normal(block:() -> Unit) {
    block()
}

能够看到咱们只需求在lambda表达式的花括号外运用@符号,并在@符号前加上咱们自界说的标签名,这样咱们就能够给一个Lambda表达式显现的声明一个标签名。如上示例代码,咱们给normal函数声明晰一个test的标签名,这样咱们在Lambda表达式中就能够运用咱们自界说的标签来完结当时高阶函数的部分回来。 为了验证带有@标签约束的return仅仅部分回来,咱们在上面main函数的首行和尾行各打印一行代码,如下:

fun main() {
    println("main called start")
    normal {
        println("normal called start")
        return@normal
        println("normal called end")
    }
    println("main called end")
}
fun normal(block:() -> Unit) {
    block()
}
// 输出
main called start
normal called  start
main called end

从上述代码的打印结果咱们能够看到retrun@normal仅仅仅仅对直接包裹它的外层函数normal进行了回来,而并没有对最外层的main函数进行回来。

2.inline

在Kotlin中运用关键字inline润饰一个函数的时分,咱们就称这个函数是内联函数。内联函数不只能够内联自己函数体内部的代码,还能够内联函数体内部函数体的代码(Lambda表达式中的代码)。下面咱们先来看一下示例代码:

fun main() {
   normal { println("normal called") }
}
fun normal(block:() -> Unit) {
    println("normal started")
    block()
    println("normal end")
}

咱们知道,Kotlin代码终究仍是要编译成Java字节码的。在Android Studio中选择Tools -> Kotlin -> Show Kotlin Bytecode,在右边弹出的方框中,咱们点击Decompile按钮。

Kotlin语法基础篇五:inline、noinline、crossinline
在上述截图中标记的2中咱们能够看到,Lambda表达式在Java中其实是用匿名内实现的。这就代表咱们每调用一次高阶函数normal就会创立一个Funciton的匿名类,这在内存上会形成额定的开销。 当咱们运用inline关键字来润饰normal函数的时分,咱们再来看一下反编译成Java字节码的状况:

Kotlin语法基础篇五:inline、noinline、crossinline
在main函数中咱们仅仅是将3处的代码替换到了2处。并没有创立额定的匿名类。咱们将上面的代码稍作更改如下:

fun main() {
   println("main started")
   normal {
       println("normal called")
       return
   }
   println("main end")
}
inline fun normal(block:() -> Unit) {
    block()
}
// 输出
main started
normal called

能够看到当咱们运用inline关键字润饰normal函数的时分,在main函数中咱们能够直接在normal函数中运用裸return来完结最外层函数main的回来。 到这儿咱们就能够总结一下inline关键字的长处和缺点了:

  1. 对于普通的函数,运用内联函数是完全没有必要的,仅仅减少了一次办法栈的调用,这种优化能够忽略
  2. 对于带有函数类型参数的高阶函数,咱们运用inline关键字润饰的内联函数,来节省Lambda表达式在调用的当地创立匿名类带来的内存开销
  3. 由于内联函数,不只能够内联自己内部的代码,还能够内联内部的内部中的代码(Lambda表达式中的代码)。在调用的当地仅仅仅仅代码的替换,咱们能够在Lambda表达式中,直接运用裸return来完结最外层函数的回来。这种回来(位于 lambda 表达式中,但退出包含它的函数)咱们称之为非部分回来。
  4. 如果需求内联的函数代码逻辑过于杂乱,调用该函数又比较频频,则会导致调用该内联函数的当地出现代码臃肿的状况。

3.noinline

在Kotlin中noinline关键字总是和inline关键字成对的出现。翻译成中文的意思便是禁用内联,咱们先来看一下,如下的代码场景:

Kotlin语法基础篇五:inline、noinline、crossinline
咱们在inline.kt的文件中界说了两个高阶函数,normal和simple。其中normal函数具有两个函数类型的参数block1和block2。simple函数具有一个和normal函数中block2类型相同的函数类型参数block。咱们在normal函数中调用了simple函数,并将block2函数类型的参数传递给了simple函数,编译器提示了语法错误。按惯例的函数调用来说,这两个函数类型是共同的,按理来说能够正常传递,那么为什么Kotlin编译器却给出了语法错误的提示呢? 事实上内联函数的函数类型参数在编译的时分是没有具体的参数类型的,由于它仅仅进行代码的替换。所以在Kotlin中有这么一个规定,内联函数的函数类型参数只能传递给内联函数。而noinline关键字在这种场景下就能够派上用场了:

Kotlin语法基础篇五:inline、noinline、crossinline
当咱们给normal函数的函数类型参数block2加上noinline关键字来禁用其内联。这个时分在咱们的高阶函数normal中的block2参数已经被取消了内联的资历,咱们再将block2传递给simple函数,编译器就不会再报语法错误提示了。

4.crossinline

在一些实践开发的场景中,内联函数内部可能会将咱们函数类型实例的调用放在一个外部上下文作用域的Lambda表达式中。关于带有上下文作用域的Lambda表达式,咱们已经在上一篇高阶函数和Lambda表达式中具体介绍,这儿就不再介绍了。例如咱们需求将UI代码放在主线程中去履行,咱们通常会这么写:

Kotlin语法基础篇五:inline、noinline、crossinline
而这个时分编译给出了语法错误的提示。这种将函数类型实例的调用嵌套在别的一个Lambda表达式中,咱们通常称为直接调用。 上面咱们提到用inline关键字润饰的内联函数,支撑非部分回来,也便是上面咱们说的支撑在Lambda表达式中运用裸return,现在问题来了。在这种直接调用函数类型实例的状态下,咱们在调用该高阶函数的Lambda的表达式中运用裸return,到底是回来它外层函数的调用,仍是再外层函数的调用呢?

Kotlin语法基础篇五:inline、noinline、crossinline
对于这种语法上的冲突,Kotlin编译器直接提示语法错误了,不允许这么调用。但咱们的事务场景又常常会遇到这种直接调用的状况。所以Koltin给咱们提供了crossinline关键字,它就像一个契约告诉编译器,我必定不会在这种直接调用函数类型实例的Lambda表达式中运用裸return,当咱们给参数block加上crossinline关键字以后,Kotlin编译器不会在报语法错误了。

Kotlin语法基础篇五:inline、noinline、crossinline

但同时咱们也向Kotlin编译器保证了,不会在调用该内联函数的时分,在Lmabda表达式中运用裸return。如上面代码示例,咱们在内联函数runInMainThead办法中已经没有办法运用裸return了,只能运用标签约束的return。

Kotlin语法基础篇五:inline、noinline、crossinline

总结

关于inline、noinline、crossinline关键字的运用到这儿就介绍完了。娴熟的把握了这一节的内容,对于咱们阅览源码和实践开发会有很大的帮助。下篇文章笔者计划结合一下自己在开发中对扩展函数和高阶函数的运用打开介绍,咱们下期再见!