Kotlin修煉指南
作用域函數(shù)
作用域函數(shù)是Kotlin中的一個(gè)非常有用的函數(shù),它主要分為兩種邪码,一種是拓展函數(shù)式宾尚,另一種是頂層函數(shù)式孝情。作用域函數(shù)的主要功能是為調(diào)用函數(shù)提供一個(gè)內(nèi)部范圍只锭,同時(shí)結(jié)合kotlin的語(yǔ)法糖提供一些便捷操作著恩。
作用域函數(shù)主要有下面這幾種,它們的主要區(qū)別就是函數(shù)體內(nèi)使用對(duì)象和返回值的區(qū)別蜻展。
- run
函數(shù)體內(nèi)使用this代替本對(duì)象喉誊。返回值為函數(shù)最后一行或者return指定的表達(dá)式
- let
函數(shù)內(nèi)使用it代替本對(duì)象。返回值為函數(shù)最后一行或者return指定的表達(dá)式铺呵。
- apply
函數(shù)內(nèi)使用this代替本對(duì)象裹驰。返回值為本對(duì)象隧熙。
- also
函數(shù)內(nèi)使用it代替本對(duì)象片挂。返回值為本對(duì)象。
- takeIf
條件為真返回對(duì)象本身否則返回null贞盯。
- takeUnless
條件為真返回null否則返回對(duì)象本身音念。
- with
with比較特殊,不是以擴(kuò)展方法的形式存在的躏敢,而是一個(gè)頂級(jí)函數(shù)闷愤。
傳入?yún)?shù)為對(duì)象,函數(shù)內(nèi)使用this代替對(duì)象件余。
返回值為函數(shù)最后一行或者return指定的表達(dá)式讥脐。
- repeat
將函數(shù)體執(zhí)行多次。
通過(guò)表格進(jìn)行下總結(jié)啼器,如下所示旬渠。
操作符 | this/it | 返回值 |
---|---|---|
let | it | 最后一行或者return指定的表達(dá)式 |
with | it | 最后一行或者return指定的表達(dá)式 |
run | this | 最后一行或者return指定的表達(dá)式 |
also | this | 上下文對(duì)象 |
apply | this | 上下文對(duì)象 |
下面通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)演示下這些作用域函數(shù)的基本使用方式。
class TestBean {
var name: String = "xuyisheng"
var age: Int = 18
}
fun main(args: Array<String>) {
val test = TestBean()
val resultRun = test.run {
name = "xys"
age = 3
println("Run內(nèi)部 $this")
age
}
println("run返回值 $resultRun")
val resultLet = test.let {
it.name = "xys"
it.age = 3
println("let內(nèi)部 $it")
it.age
}
println("let返回值 $resultLet")
val resultApply = test.apply {
name = "xys"
age = 3
println("apply內(nèi)部 $this")
age
}
println("apply返回值 $resultApply")
val resultAlso = test.also {
it.name = "xys"
it.age = 3
println("also內(nèi)部 $it")
it.age
}
println("also返回值 $resultAlso")
val resultWith = with(test) {
name = "xys"
age = 3
println("with內(nèi)部 $this")
age
}
println("with返回值 $resultWith")
test.age = 33
val resultTakeIf = test.takeIf {
it.age > 3
}
println("takeIf $resultTakeIf")
val resultTakeUnless = test.takeUnless {
it.age > 3
}
println("takeUnless $resultTakeUnless")
}
執(zhí)行結(jié)果如下所示端壳。
Run內(nèi)部 TestBean@27c170f0
run返回值 3
let內(nèi)部 TestBean@27c170f0
let返回值 3
apply內(nèi)部 TestBean@27c170f0
apply返回值 TestBean@27c170f0
also內(nèi)部 TestBean@27c170f0
also返回值 TestBean@27c170f0
with內(nèi)部 TestBean@27c170f0
with返回值 3
takeIf TestBean@27c170f0
takeUnless null
官網(wǎng)提供了一張圖來(lái)幫助開(kāi)發(fā)者選擇合適的作用域函數(shù)告丢,如下所示。
頂級(jí)函數(shù)使用場(chǎng)景
run损谦、with岖免、repeat,是比較常用的3個(gè)頂級(jí)函數(shù)照捡,它們是區(qū)別于其它幾種拓展函數(shù)類型的颅湘,它們的使用也比較簡(jiǎn)單,示例代碼如下所示栗精。
- run
fun testRun() {
var str = "I am xys"
run {
val str = "I am zj"
println(str) // I am xys
}
println(str) // I am zj
}
可以發(fā)現(xiàn)闯参,run頂級(jí)函數(shù)提供了一個(gè)獨(dú)立的作用域,可以在該作用域內(nèi)完整的使用全新的變量和屬性。
- repeat
repeat(5){
print("repeat")
}
repeat比較簡(jiǎn)單赢赊,直接將函數(shù)體按指定次數(shù)執(zhí)行乙漓。
- with
前面的代碼已經(jīng)演示過(guò)with如何使用。
with(ArrayList<String>()) {
add("a")
add("b")
add("c")
println("this = " + this)
this
}
要注意的是其返回值是根據(jù)return的類型或者最后一行代碼來(lái)進(jìn)行判斷的释移。
拓展函數(shù)使用場(chǎng)景
?.結(jié)合拓展函數(shù)
Kotlin的?操作符和作用域函數(shù)的拓展函數(shù)可以非常方便的進(jìn)行對(duì)象的判空及后續(xù)處理叭披,例如下面的例子。
// 對(duì)result進(jìn)行了判空并bindData
result?.let {
if (it.isNotEmpty()) {
bindData(it)
}
}
簡(jiǎn)化對(duì)象的創(chuàng)建
類似apply這樣的作用域函數(shù)玩讳,可以返回this的作用域函數(shù)涩蜘,可以將對(duì)象的創(chuàng)建和屬性的賦值寫(xiě)在一起,簡(jiǎn)化代碼熏纯,類似builder模式同诫,例如下面的這個(gè)例子。
// 使用普通的方法創(chuàng)建一個(gè)Fragment
fun createInstance(args: Bundle) : MyFragment {
val fragment = MyFragment()
fragment.arguments = args
return fragment
}
// 通過(guò)apply來(lái)創(chuàng)建一個(gè)Fragment
fun createInstance(args: Bundle)
= MyFragment().apply { arguments = args }
再例如下面的實(shí)現(xiàn)樟澜。
// 使用普通的方法創(chuàng)建Intent
fun createIntent(intentData: String, intentAction: String): Intent {
val intent = Intent()
intent.action = intentAction
intent.data = Uri.parse(intentData)
return intent
}
// 通過(guò)apply函數(shù)的鏈?zhǔn)秸{(diào)用創(chuàng)建Intent
fun createIntent(intentData: String, intentAction: String) =
Intent().apply { action = intentAction }
.apply { data = Uri.parse(intentData) }
以及下面的實(shí)現(xiàn)误窖。
// 正常方法
fun makeDir(path: String): File {
val result = File(path)
result.mkdirs()
return result
}
// 改進(jìn)方法
fun makeDir(path: String)
= path.let{ File(it) }.also{ it.mkdirs() }
同一對(duì)象的多次操作
在開(kāi)發(fā)中,有些對(duì)象有很多參數(shù)或者方法需要設(shè)置秩贰,但該對(duì)象又沒(méi)用提供builder方式進(jìn)行構(gòu)建霹俺,例如下面的例子。
val linearLayout = LinearLayout(itemView.context).apply {
orientation = LinearLayout.VERTICAL
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT)
}
progressBar.apply {
progress = newProgress
visibility = if (newProgress in 1..99) View.VISIBLE else View.GONE
}
不論是let毒费、run丙唧、apply還是其它拓展函數(shù),都可以實(shí)現(xiàn)這樣的需求觅玻,借助it或this想际,可以很方便的對(duì)該對(duì)象的多個(gè)屬性進(jìn)行操作。
不過(guò)這些拓展函數(shù)還是有一些細(xì)微的差別的溪厘,例如T.run和T.let(即使用it和this的區(qū)別)
- 使用it的作用域函數(shù)胡本,可以使用特定的變量名來(lái)重命名it,從而表達(dá)更清楚的語(yǔ)義桩匪。
- this在大部分情況下是可以省略的打瘪,比使用it簡(jiǎn)單
例如下面的例子。
stringResult?.let {
nonNullString ->
println("The non null string is $nonNullString")
}
通過(guò)對(duì)it的重命名傻昙,語(yǔ)義表達(dá)更加清楚闺骚。
條件操作
借助kotlin的?操作符,可以簡(jiǎn)化很多條件操作妆档,例如下面的幾個(gè)例子僻爽。
url = intent.getStringExtra(EXTRA_URL)?.takeIf { it.isNotEmpty() } ?: run {
toast("url空")
activity.finish()
}
上面的代碼演示了【從intent中取出url并在url為空時(shí)的操作】。
test.takeIf { it.name.isNotEmpty() }?.also { print("name is $it.name") } ?: print("name empty")
上面代碼演示了【從test中取出name贾惦,不為空的時(shí)候和為空的時(shí)候的操作】胸梆。
鏈?zhǔn)秸{(diào)用
作用域函數(shù)的一個(gè)非常方便的作用就是通過(guò)其返回值的改變來(lái)組裝鏈?zhǔn)秸{(diào)用敦捧。一個(gè)簡(jiǎn)單示例如下所示。
test.also {
// todo something
}.apply {
// todo something
}.name = "xys"
通過(guò)let來(lái)改變返回值碰镜,從而將不同的處理通過(guò)鏈?zhǔn)秸{(diào)用串聯(lián)起來(lái)兢卵。
val original = "abc"
// 改變值并且傳遞到下一鏈條
original.let {
println("The original String is $it") // "abc"
it.reversed() // 改變參數(shù)并且傳遞到下一鏈條
}.let {
println("The reverse String is $it") // "cba"
it.length // 改變參數(shù)類型并且傳遞到下一鏈條
}.let {
println("The length of the String is $it") // 3
}
上面的代碼借助let,可以將函數(shù)的返回值不斷進(jìn)行修改绪颖,從而直接將下一個(gè)操作進(jìn)行鏈?zhǔn)竭B接秽荤。
而使用also(即返回this的作用域函數(shù))可以將多個(gè)對(duì)同一對(duì)象的操作進(jìn)行鏈?zhǔn)秸{(diào)用,如下所示柠横。
original.also {
println("The original String is $it") // "abc"
it.reversed() // 即使我們改變它窃款,也是沒(méi)用的
}.also {
println("The reverse String is ${it}") // "abc"
it.length // 即使我們改變它,也是沒(méi)用的
}.also {
println("The length of the String is ${it}") // "abc"
}
這里只是為了演示牍氛,所以將可以寫(xiě)在同一個(gè)作用域函數(shù)中的進(jìn)行了拆分晨继。
also和let的鏈?zhǔn)秸{(diào)用,實(shí)際上各有不同的使用技巧搬俊,通過(guò)let紊扬,可以改變返回值,而通過(guò)also悠抹,可以將多個(gè)不同的原子操作通過(guò)鏈?zhǔn)竭M(jìn)行組合珠月,讓邏輯更加明朗。
國(guó)際慣例
also & apply
雖然also和apply都是返回this楔敌,但國(guó)際慣例,它們?cè)谑褂玫臅r(shí)候驻谆,還是有一些細(xì)微的差別的卵凑,also強(qiáng)調(diào)的是【與調(diào)用者無(wú)關(guān)的操作】,而apply強(qiáng)調(diào)的是【調(diào)用者的相關(guān)操作】胜臊,例如下面的這個(gè)例子勺卢。
test?.also {
println("some log")
}?.apply {
name = "xys"
}
let & run
let和run的返回值相同,它們的區(qū)別主要在于作用域內(nèi)使用it和this的區(qū)別象对。一般來(lái)說(shuō)黑忱,如果調(diào)用者的屬性和類中的屬性同名,則一般會(huì)使用let勒魔,避免出現(xiàn)同名的賦值引起混亂甫煞。
國(guó)際慣例,run通常使用在鏈?zhǔn)秸{(diào)用中冠绢,進(jìn)行數(shù)據(jù)處理抚吠、類型轉(zhuǎn)換,例如?.run{}的使用弟胀。
歡迎大家關(guān)注我的微信公眾號(hào)——Android群英傳