Kotlin 針對函數提供了幾個關鍵字 inline noinline crossinline,其涉及 Kotlin 中內聯函數和 lambda
相關的問題。java
inline
: 聲明在編譯時,將函數的代碼拷貝到調用的地方(內聯)oninline
: 聲明 inline
函數的形參中,不但願內聯的 lambda
crossinline
: 代表 inline
函數的形參中的 lambda
不能有 return
使用 inline
聲明的函數,在編譯時將會拷貝到調用的地方。閉包
定義一個sum
函數計算兩個數的和。jvm
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
函數的實現代碼被直接拷貝到了調用的地方。ui
上面兩個使用實例並無體現出 inline
的優點。當你的函數中有 lambda
形參時,inline
的優點纔會體現。spa
考慮以下代碼,會被編譯成怎樣的 Java 代碼?code
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
}
}
複製代碼
inline
,內聯函數到調用的地方,能減小函數調用形成的額外開銷,在循環中尤爲有效inline
能避免函數的 lambda
形參額外建立 Function
對象noinline
能夠拒絕形參 lambda
內聯crossinline
顯示聲明 inline
函數的形參 lambda
不能有 return
語句,避免lambda
中的 return
影響外部程序流程