Kotlin 入门笔记05 | 青训营笔记

287 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的第18天

高阶函数

Kotlin 中有函数类型这一概念,也就是说函数可以和 Int 一样,作为函数的参数和返回值。

而如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数

定义函数类型

 (String, Int) -> Unit

左边是参数,右边是返回值,没有就写 Unit ,相当于 Java 中的 void

kotlin 的 Lambda 左边的参数好像不能用括号围起来

 fun main() {
     example { s: String, i: Int ->
         println(s)
         println(i)
     }
 }
 ​
 fun example(func: (String, Int) -> Unit) {
     func("hello", 123)
 }

反编译成 Java 代码

 public final class SixthKt {
    public static final void main() {
       ExtendFuntionKt.example((Function2)null.INSTANCE);
    }
 ​
    // $FF: synthetic method
    public static void main(String[] var0) {
       main();
    }
 ​
    public static final void example(@NotNull Function2 func) {
       Intrinsics.checkNotNullParameter(func, "func");
       func.invoke("hello", 123);
    }
 }

其中 Function2 代表着两个参数的 Function 接口,类似的还有 Function1 等等。

函数引用的写法

 fun main() {
     example(::print) // 函数引用
 }
 ​
 fun print(s: String, i: Int) {
     println(s)
     println(i)
 }

内联函数

Lambda 表达式其实就是匿名内部类的简化写法,具体实现还是用的匿名内部类的形式,这样会带来额外的内存和性能开销。

而使用内联函数可以消除这种开销。

用法:在定义高阶函数时加上 inline 关键字即可。

 inline fun example(func: (String, Int) -> Unit) {
     func("hello", 123)
 }

实现原理

Kotlin 首先会将 Lambda 表达式替换到 函数实现的地方

 fun example() {
     val s = "hello"
     val i = 123
     println(s)
     println(i)
 }

然后将函数替换到调用函数的地方

 fun main() {
     val s = "hello"
     val i = 123
     println(s)
     println(i)
 }

反编译结果(部分逻辑无关代码已省略)

 public static final void main() {
     int i = 123;
     String s = "hello";
     System.out.println(s);
     System.out.println(i);
 }

noinline 与 crossinline

一个高阶函数中如果接收了两个或者更多函数类型的参数,这时我们给函数加上了inline关键字,那么Kotlin编译器会自动将所有引用的Lambda表达式全部进行内联。

如果只想内联其中一个 Lambda 表达式,可以使用 noinline 关键字

 inline fun inlineTest(block1: () -> Unit, noinline block2: () -> Unit) {
 ​
 }

noinline 关键字存在的原因:内联的函数类型参数在编译的时候会被进行代码替换,因此它没有真正 的参数属性。非内联的函数类型参数可以自由地传递给其他任何函数,因为它就是一个真实的参数,而内联的函数类型参数只允许传递给另外一个内联函数,这也是它最大的局限性。

另外,内联函数和非内联函数还有一个重要的区别,那就是内联函数所引用的Lambda表达式 中是可以使用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) { s ->
         println("lambda start")
         if (s.isEmpty()) return@printString // 如果字符串为空则不进行打印
         println(s)
         println("lambda end")
     }
     println("main end")
 }

Lambda表达式中是不允许直接 使用return关键字的,这里使用了return@printString的写法,表示进行局部返回

结果

 main start
 printString begin
 lambda start
 printString end
 main end

声明为内联函数

 inline fun printString(str: String, block: (String) -> Unit) {
     println("printString begin")
     block(str)
     println("printString end")
 }
 fun main() {
     println("main start")
     val str = ""
     printString(str) { s ->
         println("lambda start")
         if (s.isEmpty()) return
         println(s)
         println("lambda end")
     }
     println("main end")
 }

结果

 main start
 printString begin
 lambda start

将高阶函数声明成内联函数是一种良好的编程习惯,事实上,绝大多数高阶函数是可以直接声 明成内联函数的,但是也有少部分例外的情况。

 inline fun runRunnable(block: () -> Unit) {
     val runnable = Runnable {
         block() // 报错
     }
     runnable.run()
 }

IDEA 给的报错信息是 Can't inline 'block' here: it may contain non-local returns. Add 'crossinline' modifier to parameter declaration 'block'

报错原因:Lambda 的表达式默认实现方式是匿名内部类,而内联函数允许使用 return 关键字进行函数返回,但由于是在匿名内部类中调用的函数类型参数,不可能调用外层调用函数返回,最多只能对匿名内部类中的函数进行返回。

按下 Alt + Enter 自动修复

 inline fun runRunnable(crossinline block: () -> Unit) {
     val runnable = Runnable {
         block()
     }
     runnable.run()
 }

此时函数类型参数前加上了 crossinline 关键字,该关键字用于保证在内联函数的Lambda表达式中一定不会使用return关键字。

而此时如果在Lambda表达式里使用了return关键字,会报错:'return' is not allowed here ,而return@Runnable的写法仍可以使用。

参考与鸣谢

郭林《第一行代码》第三版