今天要介紹一些kotlin的特殊function用法,高階函式與lambda尤其常用,可以多留意。
將A函式作為參數傳入B函式,此B函式就是一個高階函式。
簡單舉例,我們現在寫了一個高階函式giveMoney,來決定誰要獲得多少錢。
因為pickAName這個方法符合findName方法的條件(傳入一個Int,回傳一個String),所以就可以傳入giveMoney。
fun main(args: Array<String>) {
val str = giveMoney(3,2000,::pickAName)
println(str)
}
fun giveMoney(num:Int,money:Int,findName:(number:Int)->String):String{
return "${findName(num)} has ${money} dollars"
}
fun pickAName(num:Int):String{
return when(num){
1-> "Kai"
2-> "Alvin"
3-> "joey"
else-> "WangWang"
}
}
印出結果: joey has 2000 dollars
而kotlin提供了高階函式更簡便的寫法,也就是lambda表達式。以上述例子,我們可以按以下方式呼叫giveMoney函式:
fun main(args: Array<String>) {
val str = giveMoney(3,2000,{num->
when(num){
1-> "Kai"
2-> "Alvin"
3-> "joey"
else-> "WangWang"
}
})
println(str)
}
fun giveMoney(num:Int,money:Int,findName:(number:Int)->String):String{
return "${findName(num)} has ${money} dollars"
}
呼叫giveMoney函式時,將原本的pickAName函式做為一個參數直接傳入。
此時,如果lambda運算式是函式的最後一個參數,我們可以將大括號直接搬到外面來。
fun main(args: Array<String>) {
val str = giveMoney(3,2000){num->
when(num){
1-> "Kai"
2-> "Alvin"
3-> "joey"
else-> "WangWang"
}
}
println(str)
}
而若Lambda運算式中只有一個參數,可以用it代替。
fun main(args: Array<String>) {
val str = giveMoney(3,2000){
when(it){
1-> "Kai"
2-> "Alvin"
3-> "joey"
else-> "WangWang"
}
}
println(str)
}
舉一個常見的例子,我們寫專案都會需要預防快速連續點擊的click事件,但我們不可能在每個setOnClickListener裡面都先判斷一次是否連點才開始做事。這時候高階函式與lambda就派上用場了。
fun View.setMyClickListener(act: () -> Unit){
setOnClickListener {
if(!isFastDoubleClick()){
act()
}
}
}
之後呼叫setMyClickListener只要這樣寫就好了:
button.setMyClickListener{
//do something
}
使用高階函式會有一些缺點,例如在執行時期造成記憶體的消耗。每個傳遞的lambda函式都是一個物件,且還要在函式內部存取外層變數,這些都會消耗記憶體資源。
我們可以加入inline關鍵字來宣告一個內聯函式。其作用是能夠將函式的程式碼拷貝到呼叫的地方來展開。透過將Lambda運算式內聯在使用處,可以減少上述資源的消耗。為什麼呢?
舉個例子,我們現在使用了高階函式與lambda。
fun main(args: Array<String>) {
printNames {
println("kai")
}
}
fun printNames(action:()->Unit){
println("WangWang")
action()
}
反編譯後會是這樣子,系統會建立一個function0物件來代表我們傳遞的那個action函式。它是0結尾是因為它本身沒有參數。系統仍然對外呼叫了兩個函式(Function0跟printNames),也就是說lambda只是語法糖而已,讓你寫起來方便,但是底層運作的本質上仍是不變的。
public final class MainThreadKt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
printNames((Function0)null.INSTANCE);
}
public static final void printNames(@NotNull Function0 action) {
Intrinsics.checkParameterIsNotNull(action, "action");
String var1 = "WangWang";
boolean var2 = false;
System.out.println(var1);
action.invoke();
}
}
加入inline
fun main(args: Array<String>) {
printNames {
println("kai")
}
}
inline fun printNames(action:()->Unit){
println("WangWang")
action()
}
反編譯就會長這樣,會發現println("WangWang")跟println("kai")都被直接拷貝至main方法中,減少了呼叫函式產生的額外資源消耗。也避免了函式的 lambda 形參額外建立 Function 物件
public final class MainThreadKt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
int $i$f$printNames = false;
String var2 = "WangWang";
boolean var3 = false;
System.out.println(var2);
int var4 = false;
String var5 = "kai";
boolean var6 = false;
System.out.println(var5);
}
public static final void printNames(@NotNull Function0 action) {
int $i$f$printNames = 0;
Intrinsics.checkParameterIsNotNull(action, "action");
String var2 = "WangWang";
boolean var3 = false;
System.out.println(var2);
action.invoke();
}
}