天天看點

Kotlin研發第十三彈——高階函數和lambda表達式

高階函數和lambda表達式

尾遞歸函數(tailrec)

kotlin支援函數時程式設計的尾遞歸。這個允許一些算法可以通過循環而不是遞歸解決問題,進而避免了棧溢出。當函數被标記為

tailrec

時,編譯器會優化遞歸,并用高效迅速的循環代替它

//尾遞歸
tailrec fun findFixPoint(x:Double=1.0):Double=if (x==Math.cos(x))x else findFixPoint(Math.cos(x))

           

這是計算餘弦不動點,輸出結果

0.7390851332151607
           

**tailrec:**符号使用後,最後一個操作隻能是調用函數自己,

不允許在遞歸代碼後不允許有其他代碼的

不允許在try/catch/finally塊中

目前的尾遞歸隻能在JV買的後端中用

高階函數 ?

高階函數就是可以接受函數作為參數或者傳回一個函數的函數。

比如

lock()

就是例子,接受一個lock對象和一個函數,運作函數并釋放lock

//高階函數
fun <T> lock(lock: Lock,body:()->T):T{
    lock.lock()
    try {
        return body()
    }
    finally {
        lock.unlock()
    }
}
           

如果我們想調用

lock()

函數,我們可以傳給他另外一個函數作為參數

fun toBeSynchroized() = sharedResource.operation()
val result = lock(lock, ::toBeSynchroized)
           

其中最友善的辦法是傳遞一個字面函數(通常為lambda表達式):

字面函數

字面函數被包括在大括号裡

參數在

->

前面聲明(參數類型可以省略)

函數體在

->

之後
在kotlin中有一個約定,如果某一個函數的最後一個參數是函數,并且你向那個位置傳遞一個lambda表達式,那麼你可以在括号外面定義這個lambda表達式
lock(lock){
    sharedResource.operation()
}
           

map()

(ofMapReduce):
fun <T,R> List<T>.map(transform:(T)->R):List<R>{
    val result= arrayListOf<R>()
    for (item in this){
        result.add(transform(item))
    }
    return result
}

 val doubled= arrayListOf(1,2,3).map { it->it*2 }
    println(doubled)
           

如果字面隻有一個參數,聲明可以省略,名字叫就是

it

val doubled= arrayListOf(1,2,3).map { it*2 }
    println(doubled)
           

輸出結果

[2, 4, 6]
           

并且可以寫LINQ-風格的代碼

val strings= arrayOf("a","abc","abcd","qww")
    val test=strings.filter { it.length==3 }.sortedBy { it }.map { it.toUpperCase() }
    for (ite in test){
        println(ite)
    }
           

輸出

ABC
QWW
           

内聯函數

**内聯函數時為了提高高階函數的性能:**使用高階函數會帶來一些運作時的效率損失:每一個函數都是一個對象,且這個對象捕獲了一個閉包,閉包内的變量可以在函數對象内部通路。記憶體配置設定(為函數對象和類)和實際調用将引入運作時間開銷。

但通過使用内聯

lambda表達式

,可以避免這些情況出現。

内聯函數如何運作

當一個函數被聲明

inline

時,它的函數體是内聯的,也就是說,函數體會被直接替換到函數調用的地方,如下例子:

//内聯函數
inline fun inlineTest(prefix:String,action:()->Unit){
    println("call before $prefix")
    action()
    println("call after $prefix")
}

 //内聯函數
    inlineTest("douwowan"){
        println("hahhah")
    }
           

輸出

call before douwowan
hahhah
call after douwowan
           

上面所述代碼就相當于直接輸出——編譯器編譯時顯示成這樣:

println("call before douwowan")
 action()
 println("call after douwowan")
           
高階函數的方式——傳遞函數類型的變量作為參數
val call:()->Unit={ println("hahaha")}
inlineTest("都一樣",call)
inlineTest("差不多",{println("hahaha")})
           

輸出

call before 都一樣
hahaha
call after 都一樣
call before 差不多
hahaha
call after 差不多
           

參數

函數參數是用Pascal符号定義的

name:type

。參數之間用都好隔開,每個參數必須指明類型。

預設參數

函數參數可以設定預設值,當參數被忽略時會使用預設值,

這樣比其他語言可以減少重載

預設值可以通過type類型後使用

=

進行指派

命名參數

在調用函數時可以參數命名,這對于那種有大量參數的函數很友善

fun reformat(str:String,isMale:Boolean=true,age:Int=27){...}
 TestResourceLoader().reformat("wyc")
           

使用時可以使用預設的參數來初始化方法不用過分重載,适用于某些含有預設屬性的方法

注意:命名參數文法不能用于調用Java函數中,因為Java的位元組碼不能確定方法參數命名的不變性

不帶傳回值的參數

如果函數不會反悔任何有用值,那麼他的傳回類型就是

unit

.并且可以省略

fun printHello(name: String?): Unit {
    if (name != null)
        println("Hello ${name}")
    else
        println("Hi there!")
    // `return Unit` or `return` is optional
}

           

單表達式函數

當函數隻傳回單個表達式,大括号可以省略并在

=

後面定義函數體

在編譯器可以推斷出傳回值類型的時候,傳回值可以省略

明确傳回類型

下面的例子中必須有明确傳回類型,除非他是傳回

Unit

類型。kotlin并不會對函數體重的傳回類型進行推斷,因為函數體中可能有複雜的控制流,他的傳回類型未必對讀者課件(甚至編譯器而言也有可能是不可見的)

變長參數

函數的參數(通常是最後一個參數)可以用

vararg

修飾符進行标記:

//可變長度參數
fun <T> asList(vararg ts:T):List<T>{
    val result=ArrayList<T>()
    result.addAll(ts)
    return result
}
println(asList(1,2,3,"wyc"))
           

輸出結果

标記後允許傳遞可變長度的參數

注意,隻有一個參數可以被标注

vararg

。加入

vararg

并不是清單中的最後一個參數,那麼後面的參數需要通過命名參數文法進行傳值,再或者如果這個參數是函數類型,就需要通過lambda法則。

當調用變長參數的函數時,我們可以一個一個傳遞參數,比如

asList(1,2,3)

,或者我們傳遞一個array的内容給函數,我們就可以使用

*

字首操作符:

val list= arrayOf("q","w","e","r")
    val changeResult= asList(1,2,3,"wyc",*list)
    println(changeResult)
           

繼續閱讀