高階函數和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表達式):
val result=lock(lock,{sharedResource.operation()})
字面函數
字面函數被包括在大括號裏
參數在
->
前面聲明(參數類型可以省略)函數體在
->
之後
在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
。參數之間用都好隔開,每個參數必須指明類型。
fun powerOf(number:Int,exponent:Int){}
默認參數
函數參數可以設置默認值,當參數被忽略時會使用默認值,
這樣比其他語言可以減少重載
fun read(b:Array<Byte>,off:Int=0,len:Int=b.size){}
默認值可以通過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
}
單表達式函數
當函數只返回單個表達式,大括號可以省略並在=
後面定義函數體
fun double(x:Int):Int=X*2
在編譯器可以推斷出返回值類型的時候,返回值可以省略
fun double(x:Int)=X*2
明確返回類型
下面的例子中必須有明確返回類型,除非他是返回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"))
輸出結果
[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)