佛系編碼 · 寫點編碼的日常
inline 在編譯時,會將此修飾符修飾的函數複製到調用處(稱為內聯),避免創建 Function 對象,以減少創建對象的內存開銷。noinline 需要配合 inline 使用,使用在函數型參上,告訴編譯器當前這個函數不能內聯crossinline 需要配合 inline 使用,告訴編譯器不能使用 return,否則就跳出調用處函數,但是可以使用 return@label 跳出指定外層。(本身在 lambda 內部就是不能使用 return的,而只能使用 return@label在 kotlin 中函數是一等公民,可以存儲於變量和其他數據結構中,可以當作參數傳遞,也可以當做返回類型返回。
高階函數:使用了函數參數或者返回函數類型的函數。
在編譯時,每一個函數都會被編譯為一個 FunctionX 類型,運行時,每一個函數都是一個對象。
為了優化此現象,可以使用 inline 修飾符,它會將函數的代碼內聯(複製)到調用處。
話語過於枯燥,實踐出真知。
簡單創建一個高階函數,使用了兩個函數型參。
fun cal(a: Int, b: Int, operate: (Int, Int) -> Int, result: (Int) -> Unit) {
var c = operate(a, b)
println("cal : $a ,$b = $c")
result(c)
}調用處
fun test(): Int {
var product = 0
for (i in 1 until 10) {
cal(product, i, { a, b ->
var c = a + b
println("$a + $b = $c")
c
}, {
product += it
println("product -> $product")
})
println("for -> $i")
}
println(" 是不是到了這裡,沒有到這裡,說明直接跳出 test 函數了 product ->$product")
return product
}cal 函數使用了兩個函數類型的參數,在調用處使用兩個 lambda 表達式。
在編譯時,每個 lambda 表達式都會被編譯成 FunctionX 類,有幾個參數編譯成幾.
例如這裡的 Function1 Function2 類(默認最多22個,再多就得自己寫擴展了)。
在調用處就是創建對象使用了,看一下編譯後的代碼就很明確了。
public static final void cal(int a, int b, @NotNull Function2 operate, @NotNull Function1 result) {
Intrinsics.checkParameterIsNotNull(operate, "operate");
Intrinsics.checkParameterIsNotNull(result, "result");
int c = ((Number)operate.invoke(a, b)).intValue();
String var5 = "cal : " + a + " ," + b + " = " + c;
boolean var6 = false;
System.out.println(var5);
result.invoke(c);
}
public static final int test() {
final IntRef product = new IntRef();
product.element = 0;
int i = 1;
for(byte var2 = 10; i < var2; ++i) {
cal(product.element, i, (Function2)null.INSTANCE, (Function1)(new Function1() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke(Object var1) {
this.invoke(((Number)var1).intValue());
return Unit.INSTANCE;
}
public final void invoke(int it) {
IntRef var10000 = product;
var10000.element += it;
String var2 = "product -> " + product.element;
boolean var3 = false;
System.out.println(var2);
}
}));
String var3 = "for -> " + i;
boolean var4 = false;
System.out.println(var3);
}
String var5 = " 是不是到了這裡,沒有到這裡,說明直接跳出 test 函數了 product ->" + product.element;
boolean var6 = false;
System.out.println(var5);
return product.element;
}在使用 inline修飾高階函數後,就把代碼內聯到調用處了,就不會編譯為 FunctionX 類,此舉減少了運行時的對象創建開銷。
inline fun cal(a: Int, b: Int, operate: (Int, Int) -> Int, result: (Int) -> Unit) {
var c = operate(a, b)
println("cal : $a ,$b = $c")
result(c)
}看使用了 inline 後的調用處代碼,是把代碼複製過來了,不會再創建對象去使用了。
雖然聲明處並沒有什麼改變。
public static final void cal(int a, int b, @NotNull Function2 operate, @NotNull Function1 result) {
int $i$f$cal = 0;
Intrinsics.checkParameterIsNotNull(operate, "operate");
Intrinsics.checkParameterIsNotNull(result, "result");
int c = ((Number)operate.invoke(a, b)).intValue();
String var6 = "cal : " + a + " ," + b + " = " + c;
boolean var7 = false;
System.out.println(var6);
result.invoke(c);
}
public static final int test() {
int product = 0;
int i = 1;
for(byte var2 = 10; i < var2; ++i) {
int $i$f$cal = false;
int var7 = false;
int c = product + i;
String var9 = product + " + " + i + " = " + c;
boolean var10 = false;
System.out.println(var9);
String var6 = "cal : " + product + " ," + i + " = " + c;
boolean var5 = false;
System.out.println(var6);
int var15 = false;
product += c;
var9 = "product -> " + product;
var10 = false;
System.out.println(var9);
String var3 = "for -> " + i;
$i$f$cal = false;
System.out.println(var3);
}
String var12 = " 是不是到了這裡,沒有到這裡,說明直接跳出 test 函數了 product ->" + product;
boolean var13 = false;
System.out.println(var12);
return product;
}雖然這樣避免了創建對象的內存開銷,但是也出現了一個問題,那就是代碼被內聯到調用處了,lambda內部就可以使用 return 語句了,並且會直接作用於調用處函數。
(本身 lambda 內部是不能使用 return 的)
fun test(): Int {
var product = 0
for (i in 1 until 10) {
cal(product, i, { a, b ->
var c = a + b
println("$a + $b = $c")
c
}, {
if (it > 50) { //如果使用了 inline 修飾 cal 函數會直接跳出 test 函數。因為在編譯時已經被內聯到這裡了,所以 return 就是直接在 test() 函數裡 return 了。如果不想這樣,可以用 crossinline 修飾這個型參
println("$it > 50 了,就在這停了 ")
return 50
}
product += it
println("product -> $product")
})
println("for -> $i")
}
println(" 是不是到了這裡,沒有到這裡,說明直接跳出 test 函數了 product ->$product")
return product
}這裡 就可以使用 crossinline 了。它需要配合 inline 使用,就是告訴編譯器,不能使用 return,還是得使用 return@label 來返回到指定層面。
inline fun cal(a: Int, b: Int, operate: (Int, Int) -> Int,crossinline result: (Int) -> Unit) {
var c = operate(a, b)
println("cal : $a ,$b = $c")
result(c)
}
fun test(): Int {
var product = 0
for (i in 1 until 10) {
cal(product, i, { a, b ->
var c = a + b
println("$a + $b = $c")
c
}, {
if (it > 50) { //如果使用了 inline 修飾 cal 函數會直接跳出 test 函數。因為在編譯時已經被內聯到這裡了,所以 return 就是直接在 test() 函數裡 return 了。如果不想這樣,可以用 crossinline 修飾這個型參
println("$it > 50 了,就在這停了 ")
return@cal
}
product += it
println("product -> $product")
})
println("for -> $i")
}
println(" 是不是到了這裡,沒有到這裡,說明直接跳出 test 函數了 product ->$product")
return product
}編譯後如下,已經很清晰了,return@label 語句被編譯為了 if 控制語句,並不會跳出 test() 方法
public static final int test() {
int product = 0;
int i = 1;
for(byte var2 = 10; i < var2; ++i) {
int $i$f$cal = false;
int var7 = false;
int c = product + i;
String var9 = product + " + " + i + " = " + c;
boolean var10 = false;
System.out.println(var9);
String var6 = "cal : " + product + " ," + i + " = " + c;
boolean var5 = false;
System.out.println(var6);
int var15 = false;
if (c > 50) {
var9 = c + " > 50 了,就在這停了 ";
var10 = false;
System.out.println(var9);
} else {
product += c;
var9 = "product -> " + product;
var10 = false;
System.out.println(var9);
}
String var3 = "for -> " + i;
$i$f$cal = false;
System.out.println(var3);
}
String var12 = " 是不是到了這裡,沒有到這裡,說明直接跳出 test 函數了 product ->" + product;
boolean var13 = false;
System.out.println(var12);
return product;
}有時候並不想全部的函數型參都被內聯,怎麼辦呢,noinline 就上場了。
它會告訴編譯器,這個參數不能內聯。
inline fun cal(a: Int, b: Int,noinline operate: (Int, Int) -> Int,crossinline result: (Int) -> Unit) {
var c = operate(a, b)
println("cal : $a ,$b = $c")
result(c)
}調用處代碼編譯後如下,很清晰的看到又創建了 Function2 對象。這個參數並沒有內聯。
public static final int test() {
int product = 0;
int i = 1;
for(byte var2 = 10; i < var2; ++i) {
Function2 operate$iv = (Function2)null.INSTANCE;
int $i$f$cal = false;
int c$iv = ((Number)operate$iv.invoke(product, i)).intValue();
String var7 = "cal : " + product + " ," + i + " = " + c$iv;
boolean var8 = false;
System.out.println(var7);
int var10 = false;
String var11;
boolean var12;
if (c$iv > 50) {
var11 = c$iv + " > 50 了,就在這停了 ";
var12 = false;
System.out.println(var11);
} else {
product += c$iv;
var11 = "product -> " + product;
var12 = false;
System.out.println(var11);
}
String var3 = "for -> " + i;
boolean var15 = false;
System.out.println(var3);
}
String var13 = " 是不是到了這裡,沒有到這裡,說明直接跳出 test 函數了 product ->" + product;
boolean var14 = false;
System.out.println(var13);
return product;
}其實每個 lambda 都會被編譯成對應的 Function 類型,目前默認是最多 22個參數。我使用的版本 1.4.0 。
點擊 閱讀原文 拉到最下面可看到 Functions.kt 的源碼