高階函數和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):
map()
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法則。
vararg
vararg
當調用變長參數的函數時,我們可以一個一個傳遞參數,比如
asList(1,2,3)
,或者我們傳遞一個array的内容給函數,我們就可以使用
*
字首操作符:
val list= arrayOf("q","w","e","r")
val changeResult= asList(1,2,3,"wyc",*list)
println(changeResult)