
人生苦短,我選Kotlin
——筆者
Kotlin相比Java很年輕,也更有潛力,其對(duì)函數(shù)式編程的支持也讓其代碼更加簡(jiǎn)潔,但在理解函數(shù)式編程的過程中,總會(huì)有些障礙,比如筆者在看到apply和with這兩個(gè)方法的時(shí)候,就很奇怪,看了源碼就更加糊涂了,本文以剖析apply和with這兩個(gè)方法為線索,介紹下kotlin中函數(shù)式編程相關(guān)的幾個(gè)概念。
函數(shù)類型(function type)
在函數(shù)式語言中,函數(shù)作為一種類型可以在函數(shù)間傳遞,那么如何區(qū)別不同的函數(shù)的類型呢?
將函數(shù)的入?yún)⒑头祷刂?,作為一種函數(shù)類型,比如:
(Int) -> Int 是一個(gè)函數(shù)類型,它的傳入?yún)?shù)為Int,返回類型為Int,滿足這個(gè)條件的函數(shù)為同一種類型的函數(shù)。
fun double(x: Int): Int {
return 2 * x
}
比如double這個(gè)函數(shù)的類型為(Int) -> Int,它的入?yún)⑹荌nt,返回值為Int.
由于函數(shù)為一種類型,我們可以像定義Int值一樣定義一個(gè)函數(shù)類型的變量:
val double: (Int) -> Int = fun(value: Int): Int {
return 2 * value
}
double的類型為函數(shù),函數(shù)類型為(Int) -> Int
函數(shù)作為變量可以傳遞:
val doubleCopy = double
doubleCopy(1)
此時(shí)doubleCopy的類型和double的類型相同
高階函數(shù) (high order function)
如果一個(gè)函數(shù)將一個(gè)函數(shù)作為參數(shù),或者返回一個(gè)函數(shù),那么這種函數(shù)叫做high order function(高階函數(shù))
前面提到函數(shù)可以作為變量進(jìn)行傳遞,將函數(shù)作為參數(shù)傳遞到另一個(gè)函數(shù)中,也是允許的,比如下面的例子:
// lock為高階函數(shù),接受2個(gè)參數(shù),類型分別為:Lock,() -> T
// 前者為常見的Lock類型,后者為一個(gè)函數(shù)類型,這個(gè)類型的函數(shù)入?yún)榭眨祷刂禐門
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}
// 調(diào)用方法
lock (lock,{sharedResource.operation()})
// 在kotlin中,如果一個(gè)函數(shù)的最后一個(gè)參數(shù)為函數(shù),則可以將這個(gè)函數(shù)的函數(shù)體放到括號(hào)外面,就是常見的下面這種寫法
lock (lock) {
sharedResource.operation()
}
function-with-receiver type
kotlin中存在一個(gè)比較特殊的函數(shù)類型定義,它指定了函數(shù)的receiver(看起來和擴(kuò)展方法比較像)
理解這個(gè)很關(guān)鍵,kotlin中很多基本的函數(shù)都是基于這種函數(shù)類型來實(shí)現(xiàn)的。
比如下面的代碼段中,聲明了intToLong這個(gè)函數(shù)的receiver類型為Int,只有Int類型的對(duì)象(A)可以調(diào)用intToLong這個(gè)方法,并且在intToLong函數(shù)體內(nèi),可以通過this來調(diào)用A中的函數(shù)
val intToLong: Int.() -> Long = { toLong() }
上面的代碼段中,實(shí)際上調(diào)用的是Int類型本身定義的toLong方法:
//this可以省略掉
val intToLong: Int.() -> Long = { this.toLong() }
//可以編譯通過,調(diào)用時(shí),上面一句中的this即為3
val Long a = 3.intToLong()
//不可以編譯通過,intToLong聲明了receiver,只能被Int類型調(diào)用
val Long b = "3".intToLong()
分析下appy和with方法
這兩個(gè)方法是kotlin中常用的方法,可以簡(jiǎn)化代碼,讓邏輯更加清晰整潔,但直接理解起來會(huì)有點(diǎn)繞,如果你看懂了前面講到的幾個(gè)概念之后,appy和with方法理解起來就相對(duì)容易很多
apply
apply是kotlin中常用的方法,它的官方文檔中的定義是這樣的:
apply:Calls the specified function block with this value as its receiver and returns this value.
我們來看下他的實(shí)現(xiàn)代碼:
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
這里結(jié)合前面的函數(shù)相關(guān)概念,對(duì)這個(gè)方法進(jìn)行分析:
apply為高階函數(shù),它接受一個(gè)參數(shù)block,類型為 T.() -> Unit,在apply的函數(shù)體內(nèi),調(diào)用了傳入的block這個(gè)函數(shù),然后返回調(diào)用apply函數(shù)的對(duì)象實(shí)例。
需要注意的是,block函數(shù)的類型為 function-with-receiver ,在block函數(shù)體內(nèi),可以通過this訪問到T類型的實(shí)例。
//調(diào)用方法
fun getDeveloper(): Developer {
return Developer().apply {
developerName = "Amit Shekhar"
developerAge = 22
}
}
// 等同于下面這個(gè)方法
fun getDeveloper(): Developer {
//apply 方法返回新創(chuàng)建的Developer()
return Developer().apply {
//this 為新創(chuàng)建的Developer(),可省略
this.developerName = "Amit Shekhar"
this.developerAge = 22
}
}
with
with也是比較常用的方法,它的定義是這樣的:
Calls the specified function block with the given receiver as its receiver and returns its result.
它的實(shí)現(xiàn)代碼也只有一行
public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
with為高階函數(shù),接收兩個(gè)參數(shù):receiver,類型為T,block 類型為 T.() -> R,為function-with-receiver type,只能被T類型的對(duì)象調(diào)用,同樣,在block方法體內(nèi),可以通過this來調(diào)用到receiver。with返回的類型為R,和block的返回類型相同
fun getPersonFromDeveloper(developer: Developer): Person {
return with(developer) {
Person(developerName, developerAge)
}
}
參考:
learn kotlin apply vs with
what is a receiver in kotlin
What is a purpose of Lambda's with Receiver?