Kotlin 针对函数提供了几个关键字 inline noinline crossinline,其涉及 Kotlin 中内联函数和 lambda
相关的问题。
inline
: 声明在编译时,将函数的代码拷贝到调用的地方(内联) oninline
: 声明 inline
函数的形参中,不希望内联的 lambda
crossinline
: 表明 inline
函数的形参中的 lambda
不能有 return
使用 inline
声明的函数,在编译时将会拷贝到调用的地方。
定义一个 sum
函数计算两个数的和。
fun main(args: Array<String>) { println(sum(1, 2)) } fun sum(a: Int, b: Int): Int { return a + b } 复制代码
反编译为 Java 代码:
public static final void main(@NotNull String[] args) { int var1 = sum(1, 2); System.out.println(var1); } public static final int sum(int a, int b) { return a + b; } 复制代码
正常的样子,在该调用的地方调用函数。
然后为 sum
函数添加 inline
声明:
inline fun sum(a: Int, b: Int): Int { return a + b } 复制代码
再反编译为 Java 代码:
public static final void main(@NotNull String[] args) { //... byte a$iv = 1; int b$iv = 2; int var4 = a$iv + b$iv; System.out.println(var4); } public static final int sum(int a, int b) { return a + b; } 复制代码
sum
函数的实现代码被直接拷贝到了调用的地方。
上面两个使用实例并没有体现出 inline
的优势。当你的函数中有 lambda
形参时, inline
的优势才会体现。
考虑如下代码,会被编译成怎样的 Java 代码?
fun sum(a: Int, b: Int, lambda: (result: Int) -> Unit): Int { val r = a + b lambda.invoke(r) return r } fun main(args: Array<String>) { sum(1, 2) { println("Result is: $it") } } 复制代码
反编译为 Java:
public static final int sum(int a, int b, @NotNull Function1 lambda) { //... int r = a + b; lambda.invoke(r); return r; } public static final void main(@NotNull String[] args) { //... sum(1, 2, (Function1)null.INSTANCE); } 复制代码
(Function1)null.INSTANCE
,是由于反编译器工具在找不到等效的 Java 类时的显示的结果。
我传递的那个 lambda
被转换为 Function1
类型,它是 Kotlin 函数(kotlin.jvm.functions包)的一部分,它以 1 结尾是因为我们在 lambda
函数中传递了一个参数( result:Int
)。
再考虑如下代码:
fun main(args: Array<String>) { for (i in 0..10) { sum(1, 2) { println("Result is: $it") } } } 复制代码
我在循环中调用 sum
函数,每次传递一个 lambda
打印结果。反编译为 Java:
for(byte var2 = 10; var1 <= var2; ++var1) { sum(1, 2, (Function1)null.INSTANCE); } 复制代码
可见在每次循环里都会创建一个 Function1
的实例对象。这里就是性能的优化点所在,如何避免在循环里创建新的对象?
lambda
对象 val l: (r: Int) -> Unit = { println(it) } for (i in 0..10) { sum(1, 2, l) } 复制代码
反编译为 Java:
Function1 l = (Function1)null.INSTANCE; int var2 = 0; for(byte var3 = 10; var2 <= var3; ++var2) { sum(1, 2, l); } 复制代码
只会创建一个 Function
对象
inline
: fun main(args: Array<String>) { for (i in 0..10) { sum(1, 2) { println("Result is: $it") } } } inline fun sum(a: Int, b: Int, lambda: (result: Int) -> Unit): Int { val r = a + b lambda.invoke(r) return r } 复制代码
反编译为 Java:
public static final void main(@NotNull String[] args) { //... int var1 = 0; for(byte var2 = 10; var1 <= var2; ++var1) { byte a$iv = 1; int b$iv = 2; int r$iv = a$iv + b$iv; String var9 = "Result is: " + r$iv; System.out.println(var9); } } 复制代码
lambda
代码在编译时被拷贝到调用的地方, 避免了创建 Function
对象。
class Demo(private val title: String) { inline fun test(l: () -> Unit) { println("Title: $title") // 编译错误: Public-Api inline function cannot access non-Public-Api prive final val title } // 私有的没问题 private inline fun test(l: () -> Unit) { println("Title: $title") } } 复制代码
当使用 inline
时,如果传递给 inline
函数的 lambda
,有 return
语句,那么会导致闭包的调用者也返回。
例子:
inline fun sum(a: Int, b: Int, lambda: (result: Int) -> Unit): Int { val r = a + b lambda.invoke(r) return r } fun main(args: Array<String>) { println("Start") sum(1, 2) { println("Result is: $it") return // 这个会导致 main 函数 return } println("Done") } 复制代码
反编译 Java:
public static final void main(@NotNull String[] args) { String var1 = "Start"; System.out.println(var1); byte a$iv = 1; int b$iv = 2; int r$iv = a$iv + b$iv; String var7 = "Result is: " + r$iv; System.out.println(var7); } 复制代码
反编译之后也能看到, lambda
return
之后的代码不会执行。
可以使用 return@label
语法,返回到 lambda
被调用的地方。
fun main(args: Array<String>) { println("Start") sum(1, 2) { println("Result is: $it") return@sum } println("Done") } 复制代码
当一个 inline
函数中,有多个 lambda
作为参数时,可以在不想内联的 lambda
前使用 noinline
声明.
inline fun sum(a: Int, b: Int, lambda: (result: Int) -> Unit, noinline lambda2: (result: Int) -> Unit): Int { val r = a + b lambda.invoke(r) lambda2.invoke(r) return r } fun main(args: Array<String>) { sum(1, 2, { println("Result is: $it") }, { println("Invoke lambda2: $it") } ) } 复制代码
反编译 Java:
public static final int sum(int a, int b, @NotNull Function1 lambda, @NotNull Function1 lambda2) { int r = a + b; lambda.invoke(r); lambda2.invoke(r); return r; } public static final void main(@NotNull String[] args) { byte a$iv = 1; byte b$iv = 2; Function1 lambda2$iv = (Function1)null.INSTANCE; int r$iv = a$iv + b$iv; String var8 = "Result is: " + r$iv; System.out.println(var8); lambda2$iv.invoke(r$iv); } 复制代码
第一个 lambda
内联到了调用处,而第二个使用 noinline
声明的 lambda
没有。
声明一个 lambda
不能有 return
语句(可以有 return@label
语句)。这样可以避免使用 inline
时, lambda
中的 return
影响程序流程。
inline fun sum(a: Int, b: Int, crossinline lambda: (result: Int) -> Unit): Int { val r = a + b lambda.invoke(r) return r } fun main(args: Array<String>) { sum(1, 2) { println("Result is: $it") return // 编译错误: return is not allowed here } } 复制代码