一迁央、 概述
函數(shù):也就是子程序。
高階函數(shù):在數(shù)學和計算機科學中滥崩,高階函數(shù)是至少滿足下列一個條件的函數(shù):接受一個或多個函數(shù)作為輸入岖圈,輸出一個函數(shù)
在Java中,不支持高階函數(shù)钙皮。也就是說函數(shù)不能作為參數(shù)蜂科,也不能作為返回值。
站在Java的基礎上短条,Kotlin開始支持高階函數(shù)导匣。也就是說,函數(shù)在Kotlin中成為了一級公民慌烧。
二逐抑、 函數(shù)定義
2.1函數(shù)聲明
-
Kotlin 中的函數(shù)使用 fun 關鍵字聲明。
函數(shù)的基本組成部分包括:名稱屹蚊、參數(shù)厕氨、返回值和函數(shù)體,定義形式為:
fun methodName(param: ParamType): ReturnType {
...
}
- 函數(shù)名和參數(shù)列表作為函數(shù)的唯一的標識符
2.2汹粤、參數(shù)
- 函數(shù)參數(shù)使用 Pascal 表示法定義,即 name: type命斧。每個參數(shù)必須有顯式類型,多個參數(shù)之間使用逗號分隔.
fun methodName(number: Int, exponent: String) {
...
}
2.3默認參數(shù)
函數(shù)參數(shù)可以指定默認值, 當參數(shù)省略時, 就會使用默認值. 與其他語言相比, 這種功能使得我們可以減少大 量的重載(overload)函數(shù)定義.
//在參數(shù)類型之后, 添加 = 和默認值.
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) {
...
}
- override方法與基類型方法使用相同的默認值嘱兼。override時必須省略默認參數(shù)值:
open class A {
open fun foo(i: Int = 10) { …… }
}
class B : A() {
override fun foo(i: Int) { …… } // 不能有默認值
}
- 如果一個默認參數(shù)在一個無默認值的參數(shù)之前国葬,那么該默認值只能通過使用命名參數(shù)調(diào)用該函數(shù)來使用:
fun foo(bar: Int = 0, baz: Int) { /* …… */ }
foo(baz = 1) // 使用默認值 bar = 0
2.4命名參數(shù)
調(diào)用函數(shù)時, 可以通過參數(shù)名來指定參數(shù). 當函數(shù)參數(shù)很多, 或者存在默認參數(shù)時, 指定參數(shù)名可增加可讀性.
- 通常與默認參數(shù)配合使用
例如:
fun login(name: String, no: Int = 1001, sex: Int = 0) {
println("name: $name, no:$no, sex: $sex")
}
當采用默認方式時,我們可以這樣調(diào)用(無默認值參數(shù)位于參數(shù)列表的第一個時)
login("Aaron")
其實際上相當于
login("Aaron", 101, 0)
人如果我們不需要指定所有的參數(shù), 只是修改部分默認的參數(shù)值芹壕,我們可以這樣:
register(name = "wang", no = 1003)
2.5不定數(shù)量參數(shù)
如果在函數(shù)被調(diào)用以前汇四,函數(shù)的參數(shù)(通常是參數(shù)中的最后一個)個數(shù)不能夠確定,可以采用不定量參數(shù)方式:用 vararg
修飾符標記參數(shù)踢涌。
比如在創(chuàng)建List時通孽,創(chuàng)建前并不知道預添加至List中多少數(shù)據(jù)。
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // ts 是一個 Array
result.add(t)
return result
}
調(diào)用時, 可以向這個函數(shù)傳遞不定數(shù)量的參數(shù):
val list = asList(1, 2, 3)
- 只有一個參數(shù)可以標注為
vararg
睁壁。
與Java不同的是背苦,在Kotlin中標記為vararg的參數(shù)不一定是最后一個互捌。如果標記為vararg的參數(shù)不是最后一個,那么vararg參數(shù)之后的其他參數(shù), 可以使用命名參數(shù)來傳遞參數(shù)值, 或者, 如果參數(shù)類型是函數(shù), 可以在括號之外傳遞一個 Lambda表達式.
fun main(args: Array<String>) {
fruit("apple", "banana", address = "Minhang")
}
fun fruit(vararg fruits: String, address: String) {
for (fruit in fruits) {
println("fruit:$fruit, from address: $addr")
}
}
// Log
fruit:apple, from address: Minhang
fruit:banana, from address: Minhang
- 我們調(diào)用
vararg
函數(shù)時行剂,我們可以一個接一個地傳參秕噪,例如 asList(1, 2, 3),或者厚宰,如果我們已經(jīng)有一個數(shù)組并希望將其內(nèi)容傳給該函數(shù)腌巾,我們使用伸展spread
操作符(在數(shù)組前面加 *):
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)
2.6 返回值Unit
如果一個函數(shù)不返回任何有意義的結(jié)果值,那么它的返回類型為Unit .Unit 類型只有唯一的一個值Unit,在函數(shù)中,不需要明確地返回這個值固阁。
- 返回值為Unit的函數(shù)壤躲,Unit可以省略。
fun login(name: String, no: Int = 1001, sex: Int = 0): Unit {
println("name: $name, no:$no, sex: $sex")
}
上例中的代碼等價于:
fun login(name: String, no: Int = 1001, sex: Int = 0) {
println("name: $name, no:$no, sex: $sex")
}
2.7 明確指定返回值類型
如果一個函數(shù)體由多行語句組成的代碼段备燃,那么必須明確指定返回值類型,除非函數(shù)的的返回值為Unit凌唬。
2.8 單表達式函數(shù)
如果一個函數(shù)的函數(shù)體只有一個表達式并齐,函數(shù)體可以直接寫在 “=”之后,也就是這樣:
fun double(x: Int): Int = x * 2
如果編譯器可以推斷出函數(shù)的返回值類型, 那么返回值的類型定義是可省略:
fun double(x: Int) = x * 2
2.9 返回值和跳轉(zhuǎn)
Kotlin有三種結(jié)構(gòu)型的跳轉(zhuǎn)表達式:
- return.默認返回最近的閉包函數(shù)和匿名函數(shù)
- break.終結(jié)最近的閉包循環(huán)
- continue.進行下一步最近的閉包循環(huán)
Break 與 Continue 標簽
在 Kotlin 中任何表達式都可以用標簽(label)來標記客税。 標簽的格式為標識符后跟 @
符號况褪,例如:abc@
、fooBar@
都是有效的標簽更耻。要為一個表達式加標簽测垛,我們只要在其前加標簽即可。
loop@ for (i in 1..100) {
// ……
}
現(xiàn)在秧均,我們可以用標簽限制 break 或者continue:
loop@ for (i in 1..100) {
for (j in 1..100) {
if (……) break@loop
}
}
標簽限制的 break 會跳轉(zhuǎn)到‘該標簽指定的循環(huán)’后面的執(zhí)行點(也就是最外層for循環(huán))食侮。
標簽處返回
Kotlin 的函數(shù)可以被嵌套。 標簽限制的 return 允許我們從外層函數(shù)返回目胡。 最重要的用例是返回一個lambda表達式锯七。
例如:
fun foo() {
ints.forEach {
if (it == 0) return
print(it)
}
}
return-expression從他最近的閉包函數(shù)當中返回,也就是foo.如果我們需要返回到ints.forEach的lambda表達式,我們必須給它做出標記
fun foo(ints:List<Int>) {
ints.forEach lit@ {
if (it == 0) return@lit
print(it)
}
}
現(xiàn)在誉己,它僅僅從ints.forEach的lambda表達式處返回眉尸。
- 通常情況下,使用隱喻標簽更方便(標簽與被傳入的lambda表達式的函數(shù)具有相同的名稱)巨双。
fun foo() {
ints.forEach {
if (it == 0) return@forEach
print(it)
}
}
匿名函數(shù)與lambda同理噪猾,不再贅述。
三筑累、函數(shù)的調(diào)用
3.1 傳統(tǒng)用法
函數(shù)的調(diào)用使用傳統(tǒng)的方式:
val result = double(2)
調(diào)用類的成員函數(shù)時, 使用點號標記法(dot notation):
Sample().foo() // 創(chuàng)建一個 Sample 類的實例, 然后調(diào)用這個實例的 foo 函數(shù)
3.2 中綴標記法(Infix notation)
使用中綴標記法(infix notation)來調(diào)用函數(shù), 但函數(shù)需要滿足以下條件:
① 是成員函數(shù), 或者是擴展函數(shù)
② 只有單個參數(shù)
③ 使用 infix 關鍵字標記
class Person(var name: String, var age: Int) {
// 使用infix 關鍵字標記袱蜡,該函數(shù)可被中綴標記法法調(diào)用
infix fun printName(addr: String) {
println("addr: $addr, name: $name")
}
}
fun main(args: Array) {
val person: Person = Person(“Jone”, 20)
// 使用中綴標記法調(diào)用擴展函數(shù)
person printName("AA-BB") // Log: addr: AA-BB, name: Jone
// 上面的語句等價于
person.printName("AA-BB")
}
四、函數(shù)的范圍
在Kotlin中疼阔,函數(shù)不僅僅能夠被定義為top_level戒劫,即包下的函數(shù)半夷,還可以被定義為局部函數(shù)、成員函數(shù)以及擴展函數(shù)迅细。函數(shù)的定義方式不同巫橄,其作用域也不盡相同。當然了茵典,函數(shù)的作用域還與修飾符相關湘换,具體可參考 Kotlin-可見性修飾符.
4.1 局部函數(shù)
所謂的局部函數(shù),就是定義在函數(shù)體的函數(shù)统阿。
fun function(input: String) {
val param:Int = 101
fun finctionInternal(Inputinternal:Int) {
...
}
...
finctionInternal(param)
}
- 局部函數(shù)可以訪問外部函數(shù)中的局部變量(也就是, 閉包),具體見上例彩倚。
4.2 成員函數(shù)
成員函數(shù)是指定義在類或?qū)ο笾畠?nèi)的函數(shù)。
class Sample() {
fun foo() { print("Foo") }
}
對成員函數(shù)的調(diào)用使用點號標記法扶平。
Sample().foo() // 創(chuàng)建 Sample 類的實例, 并調(diào)用 foo 函數(shù)
4.3 擴展函數(shù)
擴展函數(shù)數(shù)是指在一個類上增加一種新的行為帆离,甚至我們沒有這個類代碼的訪問權限。
- 聲明一個擴展函數(shù)结澄,我們需要用一個接收者類型也就是被擴展的類型來作為他的前綴哥谷。
/***
* 點擊事件的View擴展
* @param block: (T) -> Unit 函數(shù)
* @return Unit
*/
fun <T : View> T.click(block: (T) -> Unit) = setOnClickListener {
if (clickEnable()) {
block(it as T)
}
}
- 擴展是靜態(tài)解析的
- 如果一個類定義有一個成員函數(shù)和一個擴展函數(shù),而這兩個函數(shù)又有相同的接收者類型麻献、相同的名字并且都適用給定的參數(shù)们妥,這種情況總是取成員函數(shù)。
class C {
fun foo() { println("member") }
}
fun C.foo() { println("extension") }
如果我們調(diào)用 C 類型 c的 c.foo()
勉吻,它將輸出“member”监婶,而不是“extension”。
可空接收者
可以為可空的接收者類型定義擴展齿桃。這樣的擴展可以在對象變量上調(diào)用惑惶, 即使其值為 null,并且可以在函數(shù)體內(nèi)檢測 this == null源譬,這能讓你在沒有檢測 null 的時候調(diào)用 Kotlin 中的toString():檢測發(fā)生在擴展函數(shù)的內(nèi)部集惋。
fun Any?.toString(): String {
if (this == null) return "null"
// 空檢測之后,“this”會自動轉(zhuǎn)換為非空類型踩娘,所以下面的 toString()
// 解析為 Any 類的成員函數(shù)
return toString()
}
具體例子可參考 :
1.[Kotlin]利用擴展函數(shù)優(yōu)雅的實現(xiàn)“防止重復點擊”.
2.使用Kotlin高效地開發(fā)Android App(一).
4.4 尾遞歸函數(shù)
Kotlin支持一種稱為尾遞歸(tail recursion)的函數(shù)式編程方式.這種方式是基于函數(shù)表達式和遞歸函數(shù)刮刑,來實現(xiàn)某些基本循環(huán)的的算法,采用這種方式可以有效的避免棧溢出的危險养渴。
當函數(shù)被關鍵字tailrec修飾雷绢,同時滿足尾遞歸(tail recursion)的函數(shù)式編程方式的形式時,編譯器就會對代碼進行優(yōu)化, 消除函數(shù)的遞歸調(diào)用, 產(chǎn)生一段基于循環(huán)實現(xiàn)的, 快速而且高效的代碼理卑。
tailrec fun plus(start: Int, end: Int, result: Int): Int = if (start >= end) result else plus(start+1, end, start + result)
// Test
fun main(args: Array<String>) {
println(plus(0, 10, 0)) // 打印結(jié)果 45
}
上面的代碼計算了從start到end之間的所有數(shù)的和翘紊,并將和與初始值相加后返回。編譯器優(yōu)化產(chǎn)生的代碼等價于下面這種傳統(tǒng)方式編寫的代碼:
fun plus(start: Int, end: Int, result: Int): Int {
var res = result
var sta = start
while (sta < end) {
res += sta
sta++
}
return res
}
注:
- 要符合 tailrec 修飾符的要求, 函數(shù)必須在它執(zhí)行的所有操作的最后一步, 遞歸調(diào)用它自身
- 如果遞歸調(diào)用后還有其他邏輯代碼藐唠,不能使用尾遞歸
- 尾遞歸不能用在try/catch/finally 結(jié)構(gòu)內(nèi)
- 尾遞歸目前只能用在JVM環(huán)境內(nèi)
五帆疟、高階函數(shù)
所謂的高階函數(shù)鹉究,是一種特殊的函數(shù), 它接受函數(shù)作為參數(shù), 或者返回一個函數(shù).
fun test(a: Int, b: Int, sumSom: (Int, Int, Int) -> Int): Int {
if (a > b) {
return sumSom(0, a, 0)
} else {
return sumSom(0, b, 0)
}
}
tailrec fun sumSom(start: Int, end: Int, result: Int): Int {
var res = result
var sta = start
while (sta <= end) {
res += sta
sta++
}
return res
}
// 測試類
fun main(args: Array<String>) {
println(test(10, 9, ::sumSom)) // Log:55
}
從上述代碼,在函數(shù)test中,sumSom參數(shù)是一個函數(shù)類型:(Int, Int, Int) -> Int踪宠,其是一個函數(shù)自赔,接受3個Int參數(shù),返回值是一個Int類型的值柳琢。在test中绍妨,對傳入的參數(shù)a,b進行判斷,然后執(zhí)行sumSom()函數(shù)并將執(zhí)行結(jié)果返回柬脸。
5.1 函數(shù)類型(Function Type)
對于接受另一個函數(shù)作為自己參數(shù)的函數(shù), 我們必須針對這個參數(shù)指定一個函數(shù)類型. 比如, 前面提到的test函數(shù), 它的定義如下:
fun test(a: Int, b: Int, sumSom: (Int, Int, Int) -> Int): Int {
if (a > b) {
return sumSom(0, a, 0)
} else {
return sumSom(0, b, 0)
}
}
參數(shù)sumSom的類型是(Int, Int, Int) -> Int他去,也就是說,它是一個函數(shù)倒堕,接受三個Int類型參數(shù)灾测,并且返回一個Int。
5.2 Lambda表達式與匿名函數(shù)
Lambda 表達式, 或者匿名函數(shù), 是一種”函數(shù)字面值(function literal)”, 也就是, 一個沒有聲明的函數(shù), 但是立即作為表達式傳遞出去.
max(strings, { a, b -> a.length() < b.length() })
函數(shù) max 是一個高階函數(shù), 也就是說, 它接受一個函數(shù)值作為第二個參數(shù). 第二個參數(shù)是一個表達式, 本身又是另一個函數(shù), 也就是說, 它是一個函數(shù)字面量. 作為函數(shù), 它等價于:
fun compare(a: String, b: String): Boolean = a.length() < b.length()
Lambda表達式
Lambda 表達式的完整語法形式, 也就是, 函數(shù)類型的字面值, 如下:
val sum = { x: Int, y: Int -> x + y }
① Lambda 表達式用大括號括起,
② 它的參數(shù)(如果存在的話)定義在 -> 之前 (參數(shù)類型可以省略),
③ 函數(shù)體定義在 -> 之后 (如果存在 -> 的話).
- 如果Lambda 表達式只有唯一一個參數(shù),在Kolin中可以自行判斷出Lambda表達式的參數(shù)定義涩馆,此時允許我們省略唯一一個參數(shù)的定義, 并且會為我們隱含地定義這個參數(shù), 使用的參數(shù)名為 it:
ints.filter { it > 0 } // 這個函數(shù)字面值的類型是 '(it: Int) -> Boolean'
- 如果一個函數(shù)接受另一個函數(shù)作為它的最后一個參數(shù), 那么Lambda表達式作為參數(shù)時, 可以寫在圓括號之外.
可參考View.OnClickListener在Kotlin中的進化
匿名函數(shù)
匿名函數(shù)看起來與通常的函數(shù)聲明很類似, 區(qū)別在于省略了函數(shù)名行施,函數(shù)體可以是一個表達式(如上例), 也可以是多條語句組成的代碼段:
fun(x: Int, y: Int): Int {
return x + y
}
參數(shù)和返回值類型的聲明與通常的函數(shù)一樣, 但如果參數(shù)類型可以通過上下文推斷得到, 那么類型聲明可以省略:
ints.filter(fun(item) = item > 0)
對于匿名函數(shù), 返回值類型的自動推斷方式與通常的函數(shù)一樣: 如果函數(shù)體是一個表達式, 那么返回值類型
可以自動推斷得到, 如果函數(shù)體是多條語句組成的代碼段, 則返回值類型必須明確指定(否則被認為是
Unit ).
5.3 內(nèi)聯(lián)函數(shù)
高階函數(shù)會帶來一些運行時的效率損失:每一個函數(shù)都是一個對象,并且會捕獲一個閉包( 即那些在函數(shù)體內(nèi)會訪問到的變量)魂那。 內(nèi)存分配(對于函數(shù)對象和類)和虛擬調(diào)用會引入運行時間開銷。
在許多情況下通過內(nèi)聯(lián)化 lambda 表達式可以消除這類的開銷稠项。
以 lock()
函數(shù)為例:
lock(l) { foo() }
編譯器沒有為參數(shù)創(chuàng)建一個函數(shù)對象并生成一個調(diào)用涯雅。取而代之,編譯器可以生成以下代碼:
l.lock()
try {
foo()
}
finally {
l.unlock()
}
這個才是我們想要的展运。為了讓編譯器這么做活逆,我們需要使用inline
修飾符標記 lock()
函數(shù):
inline fun <T> lock(lock: Lock, body: () -> T): T {
// ……
}
內(nèi)聯(lián)可能導致生成的代碼增加;不過如果我們使用得當(即避免內(nèi)聯(lián)過大函數(shù))拗胜,性能上會有所提升蔗候,尤其是在循環(huán)中的“超多態(tài)(megamorphic)”調(diào)用處。
通過
noinline
禁用內(nèi)聯(lián)
如果你只想被(作為參數(shù))傳給一個內(nèi)聯(lián)函數(shù)的 lamda 表達式中只有一些被內(nèi)聯(lián)埂软,你可以用 noinline
修飾符標記一些函數(shù)參數(shù):
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
// ……
}
參考:
https://hltj.gitbooks.io/kotlin-reference-chinese/content/txt/functions-and-lambdas.html
https://blog.csdn.net/io_field/article/details/53365834