前言
kotlin官網(wǎng)和kotlin教程學(xué)習(xí)教程的筆記。
一、函數(shù)使用
1.中綴標記法(infix notation)
(1) 咱們一直使用的user.foo(12),是使用的點號標記法(dot notation),這里我們學(xué)一個中綴標記法。
中綴標記法必須滿足以下條件
- 是成員函數(shù)或者是擴展函數(shù)
- 只有單個參數(shù)
- 使用infix標記
infix fun iFoo(x:Int){ //必須要有一個參數(shù)
}
user iFoo 12 //等價于 user.iFoo(12)
(2) 函數(shù)參數(shù)可以指定默認值, 當參數(shù)省略時, 就會使用默認值, 這種功能使得我們可以減少大量的重載(overload)函數(shù)定義.
fun foo(x:Int,y:Int=0,z:Int=0){}
user.foo(1) //等價于user.foo(1,0,0)
user.foo(1,2) //等價于user.foo(1,2,0)
user.foo(1,z=1) //等價于user.foo(1,0,1)
(3) 不定數(shù)量參數(shù)(Varargs)
fun foo(vararg x: Any) {
for (i in x) {
print("$i ")
}
println()
}
user.foo(1,2,3,4) //輸出1 2 3 4
val a = arrayOf(5, 6, 7)
// 展開操作符(在數(shù)組之前加一個*)
user.foo(*a) //輸出5 6 7
2. 尾遞歸函數(shù)(Tail recursive function)
Kotlin 支持一種稱為 尾遞歸(tail recursion) 的函數(shù)式編程方式. 這種方式使得某些本來需要使用循環(huán)來實 現(xiàn)的算法, 可以改用遞歸函數(shù)來實現(xiàn), 但同時不會存在棧溢出(stack overflow)的風(fēng)險. 當一個函數(shù)標記為tailrec , 并且滿足要求的形式, 編譯器就會對代碼進行優(yōu)化, 消除函數(shù)的遞歸調(diào)用, 產(chǎn)生一段基于循環(huán)實現(xiàn) 的, 快速而且高效的代碼.
tailrec fun findFixPoint(x:Double=1.0):Double=
if (x==Math.cos(x)) x else findFixPoint(Math.cos(x))
要符合 tailrec 修飾符的要求, 函數(shù)必須在它執(zhí)行的所有操作的最后一步, 遞歸調(diào)用它自身. 如果在這個遞 歸調(diào)用之后還存在其他代碼, 那么你不能使用尾遞歸, 而且你不能將尾遞歸用在 try/catch/finally 結(jié)構(gòu)內(nèi). 尾 遞歸目前只能用在 JVM 環(huán)境內(nèi)
3. 帶有接受者的函數(shù)字面值
sum : Int.(other: Int) -> Int
調(diào)用
1.sum(2)
二、lambda表達式
- Lambda 表達式用大括號括起,
- 它的參數(shù)(如果存在的話)定義在 -> 之前 (參數(shù)類型可以省略)
- (如果存在 -> 的話)函數(shù)體定義在 -> 之后.
val sum = { x: Int, y: Int -> x + y }
三、匿名函數(shù)
匿名函數(shù)看起來與通常的函數(shù)聲明很類似, 區(qū)別在于省略了函數(shù)名。
匿名函數(shù)示例:
fun(x: Int, y: Int): Int {
return x + y
}
// 使用匿名函數(shù):
(1..100).filter { it % 3 == 0 }
(1..100).filter { a -> a % 3==0 }
(1..100).filter(fun(item) = item % 3 == 0)
四、高階函數(shù)
定義:高階函數(shù)(higher-order function)是一種特殊的函數(shù), 它接受函數(shù)作為參數(shù), 或者返回一個函數(shù)。
舉個例子:
fun <T> with(t: T, body: T.() -> Unit) {
t.body()
}
這個函數(shù)接收一個 T 類型的對象和一個被作為擴展函數(shù)的函數(shù)body。它的實現(xiàn)僅僅是讓這個對象去執(zhí)行這個函數(shù)(這里執(zhí)行函數(shù)body,在調(diào)用的時候?qū)崿F(xiàn)這個函數(shù)body)。因為第二個參數(shù)是一個函數(shù),所以我們可以把它放在圓括號外面,所以我們可以創(chuàng)建一個代碼塊,在這這個代碼塊中我們可以使用 this 和直接訪問所有的public的方法和屬性:
with(movie){
titleTxt="$title"
}
調(diào)用時傳入了一個Movie對象,和實現(xiàn)的body函數(shù){ titleTxt="$title"}。
例如lock()函數(shù)
/**
* body 參數(shù)是一個函數(shù)類型: () -> T, 因此它應(yīng)該是一個函數(shù), 沒有參數(shù), 返回一個T類型的值
* body 函數(shù)在try塊內(nèi)被調(diào)用, 被lock鎖保護住, 它的執(zhí)行結(jié)果被lock()函數(shù)當作自己的結(jié)果返回.
*/
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body() //這里調(diào)用函數(shù){1*2},返回結(jié)果2
} finally {
lock.unlock()
}
}
val l = lock(lock){
1 * 2 //如果有返回值,返回值為函數(shù)塊最后一行,在這里就是1*2的結(jié)果
}
在比如,下邊這個map,沒有傳入對象,只傳入了一個函數(shù):
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
}
fun main(){
val result = listOf(1,2,3).map { a->a*2 }
//等價于,val result = listOf(1,2,3).map { it*2 } // 如果不自己命名為a,則默認為it
}
五、內(nèi)聯(lián)函數(shù)
使用 高階函數(shù) 在運行時會帶來一些不利: 每個函數(shù)都是一個對象, 而且它還要捕獲一個閉包(在函數(shù)體內(nèi)部訪問的那些外層變量). 內(nèi)存占用(函數(shù)對象和類都會占用內(nèi)存) 以及虛方法調(diào)用都會帶來運行時的消耗.
1. inline
一個內(nèi)聯(lián)函數(shù)會在編譯的時候被替換掉,而不是真正的方法調(diào)用。這在一些情況下可以減少內(nèi)存分配和運行時開銷。舉個例子,如果我們有一個函數(shù),只接收一個函數(shù)作為它的參數(shù)。如果是一個普通的函數(shù),內(nèi)部會創(chuàng)建一個含有那個函數(shù)的對象。另一方面內(nèi)聯(lián)函數(shù)會把我們調(diào)用這個函數(shù)的地方替換掉,所以它不需要為此生成一個內(nèi)部的對象。
例如:
inline fun supportsLollipop(code: () -> Unit) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
code()
}
}
supportsLollipop {
window.setStatusBarColor(Color.BLACK)
}
編譯時,代碼就是這樣子的
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.setStatusBarColor(Color.BLACK)
}
2. noinline
如果一個內(nèi)聯(lián)函數(shù)的參數(shù)中有多個 Lambda 表達式, 而你只希望內(nèi)聯(lián)其中的一部分, 你可以對函數(shù)的一部分參數(shù)添加 noinline 標記
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { // ...
}
注:函數(shù)內(nèi)聯(lián)也許會導(dǎo)致編譯產(chǎn)生的代碼尺寸變大, 但如果我們使用合理(不要內(nèi)聯(lián)太大的函數(shù)), 可以換來性能的提高