正文
Kotlin 支持把函数赋值给变量并传递变量作为其他函数的参数。接受其他函数作为参数的函数称为高阶函数。一个 Kotlin 函数可以由它的名字加前缀 :: 而引用,或直接在代码块中声明一个匿名函数,或使用 lambda 表达式语法。此中第三种是描述一个函数的最紧凑的方式。
Kotlin 是给 Java 6/7 JVM 和 Android 提供 lambda 表达式支持的最佳方式之一。
考虑下面的实用函数,它在数据库事务中执行任意操作,并返回受影响行的数量:
我们可以通过使用类似于 Groovy 的语法传递一个 lambda 表达式作为最后的参数:
但 Java 6 JVM 不直接支持 lambda 表达式。那么它们是如何转换成字节码的呢?也许正如你所期望的, lambda 表达式和匿名函数被编译为函数对象。
这是 Lambda 表达式编译后对应的 Java 呈现。
在 Android dex 文件中,每个 Lambda 表达式都被编译成函数,实际上它会导致增加 3 到 4 个方法。
好消息是这些函数对象的新实例只会在必要的时候创建,这在实践中就意味着:
[译者注:捕获 Lambda 表达式指在表达式内部访问了表达式外的非静态变量或者对象的表达式,非捕获 Lambda 表达式反之]
既然我们示例中的调用代码使用了非捕获 Lambda,它会被编译成单例而不是一个内部类:
如果标准(非内联)的高阶函数调用捕获 Lambda,应该避免对其进行重复调用以减少垃圾回收器的压力。
与 Java 8 不同,Java 8 大约有43 个不同的特定函数接口用于尽可能地避免装箱和拆箱。由 Kotlin 编译出来的函数对象只实现完全通用的接口,高效地将 Object 类型用于输入或输出值。
这表示在高阶函数中调用作为参数传入的函数,如果函数涉及基本类型(比如 Int 或 Long),实际上会对输入值和返回值有计划地装箱和拆箱。这可能会对性能产生不可忽视的影响,尤其是在 Android 中。
在我们上面编译的 Lambda 中,你可以看到结果是装箱成 Integer 对象的。调用者代码随后会对其进行拆箱操作。
在写一个函数作为参考的标准(非内联)高阶函数时,如果这个作为参数的函数使用的是基本类型的输入或输出值,那就要小心了。调用这个作为参数的函数会因为装箱和拆箱操作给垃圾收集器带来更大的压力。