認(rèn)識(shí)kotlin中的let、with强经、run睡陪、also、apply匿情、map兰迫、flatMap等操作符。
從java轉(zhuǎn)到kotlin遇到的第一個(gè)障礙就是kotlin自帶的操作符炬称,在看別人代碼的時(shí)候總是被各種各樣的操作符弄的一頭霧水汁果。為什么他可以這么寫(xiě)?為什么他可以直接使用對(duì)象的屬性玲躯?這一系列的代碼執(zhí)行之后到底變成了什么樣子据德?伴隨著各種各樣的問(wèn)題,我們不得不先學(xué)習(xí)一下kotlin的操作符跷车。
本文對(duì)kotlin中常用的操作符進(jìn)行舉例說(shuō)明棘利,方便開(kāi)發(fā)者理解使用。
1. 基礎(chǔ)操作符
1.1 let
將調(diào)用者傳入代碼塊中姓赤,以
it
指代傳入的對(duì)象,執(zhí)行代碼塊的代碼仲吏,將代碼塊最后一行結(jié)果或return指定的數(shù)據(jù)返回不铆。
let
的執(zhí)行效果和把代碼寫(xiě)在代碼塊外面差不多蝌焚,主要的作用是可以對(duì)test
變量是否為空做出判斷,如果test
為空則不會(huì)執(zhí)行代碼塊中的代碼誓斥。對(duì)于代碼的閱讀性有一定的提升庵寞,業(yè)務(wù)邏輯和臨時(shí)變量都寫(xiě)在代碼塊中齐疙,方便區(qū)分。
test1("test1")
private fun test1(input: String?) {
// 返回一個(gè)字符串中的第一個(gè)數(shù)字字符所對(duì)應(yīng)的數(shù)字,找不到則返回null
val result = input?.let { // 如果input為空拦英,則不會(huì)執(zhí)行l(wèi)et代碼塊的代碼,直接返回null
var number: Int? = null
for (i in it.iterator()) { // 調(diào)用傳入對(duì)象的方法需要使用it引用
if (Character.isDigit(i)) {
number = Integer.parseInt(i.toString())
break
}
}
number
}
LogUtil.print("result = $result") // result = 1
}
1.2 with
傳入一個(gè)對(duì)象悬槽,在對(duì)象內(nèi)部執(zhí)行代碼塊中的代碼穗酥,可以直接調(diào)用傳入對(duì)象的公共方法及屬性,也可以使用
this
指代傳入對(duì)象進(jìn)行操作框仔,將代碼塊最后一行結(jié)果或return指定的數(shù)據(jù)返回舀武。
with
操作符不好用,他無(wú)法以鏈?zhǔn)秸{(diào)用的方式承接上面的數(shù)據(jù)离斩,如果傳入的對(duì)象可能為空银舱,在使用的時(shí)候依舊需要對(duì)空指針進(jìn)行判斷。一般在需要重復(fù)多次調(diào)用同一個(gè)對(duì)象時(shí)可以使用這個(gè)操作符跛梗,可以省去調(diào)用對(duì)象的名稱(chēng)寻馏。
test2("test2")
private fun test2(input: String?) {
// 返回一個(gè)字符串中的第一個(gè)數(shù)字字符所對(duì)應(yīng)的數(shù)字,找不到則返回null
val result = with(input) {
if (this == null) {
return@with null
}
var number: Int? = null
for (i in iterator()) { // 此處可以直接調(diào)用String.iterator()方法
if (Character.isDigit(i)) {
number = Integer.parseInt(i.toString())
break
}
}
number
}
LogUtil.print("result = $result") // result = 2
}
1.3 run
將調(diào)用者傳入代碼塊中核偿,在調(diào)用者內(nèi)部執(zhí)行代碼诚欠,可以直接調(diào)用傳入對(duì)象的公共方法及屬性,也可以使用
this
指代傳入對(duì)象進(jìn)行操作宪祥,將代碼塊最后一行結(jié)果或return指定的數(shù)據(jù)返回聂薪。
run
操作符是let
和with
的結(jié)合體,將他們的優(yōu)點(diǎn)集中到一起蝗羊,既可以插入到鏈?zhǔn)秸{(diào)用中藏澳,又能直接在代碼塊中調(diào)用傳入對(duì)象的公共方法及屬性,而且在調(diào)用前進(jìn)行空指針判斷也很方便耀找。
test3("test3")
private fun test3(input: String?) {
// 返回一個(gè)字符串中的第一個(gè)數(shù)字字符所對(duì)應(yīng)的數(shù)字翔悠,找不到則返回null
val result = input?.run { // 如果input為空,則不會(huì)執(zhí)行run代碼塊的代碼野芒,直接返回null
var number: Int? = null
for (i in iterator()) { // 此處可以直接調(diào)用String.iterator()方法
if (Character.isDigit(i)) {
number = Integer.parseInt(i.toString())
break
}
}
number
}
LogUtil.print("result = $result") // result = 3
}
1.4 also
將調(diào)用者傳入代碼塊中蓄愁,以
it
指代傳入的對(duì)象,執(zhí)行代碼塊的代碼狞悲,代碼執(zhí)行完成后將調(diào)用對(duì)象返回撮抓。
also
和let
的使用方法和執(zhí)行效果差不多,唯一的區(qū)別是also
返回的是調(diào)用者本身摇锋。
test4("test4")
private fun test4(input: String?) {
// 創(chuàng)建一個(gè)內(nèi)容為輸入字符串丹拯,字號(hào)為20sp站超,顏色為白色的TextView
val textView = TextView(this)
val result = textView.also { // 此處可以直接將also連接在構(gòu)造函數(shù)后,能夠減少一個(gè)臨時(shí)變量
it.text = input ?: ""
it.textSize = 20f
it.setTextColor(0xFFFFFFFF.toInt()) // 最終的返回值為調(diào)用者乖酬,并不是最后一行代碼的值
}
LogUtil.print("result = ${result.text}") // result = test4
}
1.5 apply
將調(diào)用者傳入代碼塊中死相,在調(diào)用者內(nèi)部執(zhí)行代碼,可以直接調(diào)用傳入對(duì)象的公共方法及屬性咬像,也可以使用
this
指代傳入對(duì)象進(jìn)行操作算撮,執(zhí)行代碼塊的代碼,代碼執(zhí)行完成后將調(diào)用對(duì)象返回县昂。
apply
是對(duì)also
的升級(jí)肮柜,調(diào)用方法和屬性時(shí)不用再使用it
調(diào)用。也可以看作是run
的變種七芭,使用方法和run
一致素挽,最終返回傳入的對(duì)象。apply
常用于設(shè)置一個(gè)對(duì)象的多個(gè)屬性狸驳,對(duì)于不支持鏈?zhǔn)秸{(diào)用的對(duì)象预明,可以提供一個(gè)類(lèi)似鏈?zhǔn)秸{(diào)用的效果。
test5("test5")
private fun test5(input: String?) {
// 創(chuàng)建一個(gè)內(nèi)容為輸入字符串耙箍,字號(hào)為20sp撰糠,顏色為白色的TextView
val textView = TextView(this)
val result = textView.apply { // 此處可以直接將apply連接在構(gòu)造函數(shù)后,能夠減少一個(gè)臨時(shí)變量
text = input ?: ""
textSize = 20f
setTextColor(0xFFFFFFFF.toInt()) // 最終的返回值為調(diào)用者辩昆,并不以最后一行代碼的值為準(zhǔn)
}
LogUtil.print("result = ${result.text}") // result = test5
}
1.6 forEach & forEachIndexed
遍歷一個(gè)列表阅酪,對(duì)實(shí)現(xiàn)Iterable接口的對(duì)象進(jìn)行遍歷,將列表中的每一個(gè)數(shù)據(jù)提取出來(lái)傳遞到代碼塊中汁针,forEach會(huì)將數(shù)據(jù)用it指定并傳入代碼塊中术辐,forEachIndexed則會(huì)多傳遞一個(gè)index,用于標(biāo)記當(dāng)前數(shù)據(jù)的位置施无。
forEach和forEachIndexed并不會(huì)返回任何數(shù)據(jù)
val list = listOf(1, 2, 3, 4, 5)
list.forEach {
LogUtil.print(it)
}
list.forEachIndexed { index, i ->
LogUtil.print("$index - $i")
}
1.7 小結(jié)
- 以上“在對(duì)象內(nèi)部執(zhí)行代碼”的說(shuō)法是方便開(kāi)發(fā)者理解辉词,實(shí)際的執(zhí)行位置并不在對(duì)象內(nèi)部,所以只能調(diào)用對(duì)象的公共方法及屬性猾骡,但代碼書(shū)寫(xiě)方式卻和在對(duì)象內(nèi)部書(shū)寫(xiě)私有方法一樣瑞躺。
-
let
、run
兴想、apply
幢哨、also
操作符直接寫(xiě)在函數(shù)中時(shí),調(diào)用者為函數(shù)所在對(duì)象嫂便。 -
let
捞镰、with
、run
均是以閉包形式執(zhí)行,返回的數(shù)據(jù)為return數(shù)據(jù)或最后一行代碼的值岸售。 -
apply
几迄、also
的返回值均是調(diào)用者自身。 - 一般情況下使用
run
和apply
就足以滿足業(yè)務(wù)需求冰评,其他三個(gè)操作符了解運(yùn)行效果,能夠讀懂別人的代碼即可木羹。
2. 流程操作符
以上的基礎(chǔ)操作符也可用于流程中的數(shù)據(jù)處理甲雅。
在kotlin之前使用過(guò)RxJava,kotlin的流程操作符和RxJava差不多坑填,在開(kāi)發(fā)過(guò)程中可以直接使用kotlin內(nèi)置的操作符而不需要再引入第三方庫(kù)了抛人。
2.1 map
一對(duì)一的轉(zhuǎn)換,將n個(gè)數(shù)據(jù)的列表轉(zhuǎn)換成n個(gè)數(shù)據(jù)的列表脐瑰,類(lèi)型及數(shù)據(jù)都可以變換妖枚。僅適用于列表或可以轉(zhuǎn)換成列表的數(shù)據(jù),準(zhǔn)確的說(shuō)是實(shí)現(xiàn)了
kotlin.collections.Iterable<T>
接口的對(duì)象(例如:String會(huì)轉(zhuǎn)換成List<Char>進(jìn)行處理)苍在。map
操作符會(huì)把列表中的每一個(gè)數(shù)據(jù)提取出來(lái)绝页,用it
指定,然后執(zhí)行代碼塊中的代碼寂恬,返回return指定的數(shù)據(jù)或最后一行代碼的值续誉。當(dāng)我們需要依次處理一個(gè)列表中的每個(gè)數(shù)據(jù)的時(shí)候就可以使用
map
操作符,相當(dāng)于java的for-each循環(huán)初肉。和RxJava中的map效果一樣酷鸦。這個(gè)流程對(duì)數(shù)據(jù)的數(shù)量不會(huì)有影響。
val inputList = listOf(5, 4, 3, 2, 1) //創(chuàng)建一個(gè)包含5個(gè)數(shù)字的列表牙咏,類(lèi)型為L(zhǎng)ist<Int>
val result = inputList.map { // it指代當(dāng)前處理的數(shù)據(jù)
if (it == 1) {
return@map "first"
}
"index_$it"
}
LogUtil.print("result = $result") // result = [index_5, index_4, index_3, index_2, first]
示例中輸入的是5個(gè)int數(shù)字臼隔,我們通過(guò)判斷將值為1的數(shù)字修改為“first”,其余數(shù)字則添加“index_”前綴妄壶,最終輸出的是一個(gè)字符串?dāng)?shù)組摔握。建議返回同樣類(lèi)型的數(shù)據(jù),這樣后續(xù)繼續(xù)處理也會(huì)方便一些盯拱,如果返回的數(shù)據(jù)類(lèi)型不一致盒发,得到的列表類(lèi)型會(huì)是Any
,不方便繼續(xù)處理數(shù)據(jù)狡逢。
2.2 flatMap
一對(duì)多的轉(zhuǎn)換宁舰,將n個(gè)數(shù)據(jù)的列表根據(jù)處理邏輯轉(zhuǎn)換成m個(gè)數(shù)據(jù)的列表,類(lèi)型及數(shù)據(jù)都可以變換奢浑。使用要求和方式與
map
一樣蛮艰,但代碼塊中返回的結(jié)果要求是一個(gè)列表。最終的結(jié)果是將所有返回列表的數(shù)據(jù)連到一起雀彼,組成一個(gè)新列表壤蚜。
flatMap
對(duì)返回列表的數(shù)據(jù)個(gè)數(shù)不做限制即寡,我們可以通過(guò)flatMap
操作符調(diào)整列表中數(shù)據(jù)的個(gè)數(shù),也可以將細(xì)分的數(shù)據(jù)提到上層處理袜刷。當(dāng)我們需要把一些對(duì)象中的子數(shù)據(jù)提取到一個(gè)列表中時(shí)聪富,使用flatMap
就很方便。
val inputList = listOf(5, 4, 3, 2, 1) //創(chuàng)建一個(gè)包含5個(gè)數(shù)字的列表著蟹,類(lèi)型為L(zhǎng)ist<Int>
val result = inputList.flatMap {
if (it <= 1) {
return@flatMap listOf("$it")
}
val index: MutableList<String> = mutableListOf()
for (i in 1..it) { // 這里把傳入的數(shù)據(jù)當(dāng)作循環(huán)次數(shù)使用墩蔓,如果傳入數(shù)據(jù)是個(gè)數(shù)據(jù)模型,也可以直接提取其中的列表數(shù)據(jù)萧豆。
index.add("$it-$i")
}
index
}
LogUtil.print("result = $result")
// result = [5-1, 5-2, 5-3, 5-4, 5-5, 4-1, 4-2, 4-3, 4-4, 3-1, 3-2, 3-3, 2-1, 2-2, 1]
示例中的MutableList
是一個(gè)可變列表奸披,kotlin中分為可變列表和不可變列表,當(dāng)需要?jiǎng)討B(tài)修改列表數(shù)據(jù)個(gè)數(shù)的時(shí)候就要使用可變列表涮雷。我們通過(guò)flatMap
操作符對(duì)原始列表進(jìn)行展開(kāi)處理阵面,最終的結(jié)果是將我們每次返回的列表整合成一個(gè)新的列表。
2.3 use
可以自動(dòng)關(guān)閉使用的資源洪鸭,針對(duì)的是實(shí)現(xiàn)了
Closeable
接口的數(shù)據(jù)样刷。use
操作符會(huì)把使用的對(duì)象傳遞到代碼塊中,用it
指定览爵,然后執(zhí)行代碼塊中的代碼颂斜,返回return指定的數(shù)據(jù)或最后一行代碼的值。使用這個(gè)操作符可以代替?zhèn)鹘y(tǒng)的
try-catch-finally
代碼塊拾枣,而且不會(huì)影響流式代碼的結(jié)構(gòu)沃疮。每次使用需要手動(dòng)關(guān)閉的對(duì)象時(shí)就可以使用use
操作符簡(jiǎn)化代碼了。
BufferedReader(InputStreamReader(FileInputStream(File("a.txt")), Charsets.UTF_8)).use {
val content = it.readLine()
LogUtil.print(content)
}
這個(gè)示例展示了一個(gè)按行讀取文件的效果梅肤,先后構(gòu)建了File
司蔬、FileInputStream
、InputStreamReader
姨蝴、BufferedReader
俊啼,最終通過(guò)use
操作符自動(dòng)關(guān)閉了所有的資源。(BufferedReader
在關(guān)閉的時(shí)候會(huì)自動(dòng)關(guān)閉引用的InputStreamReader
左医,所以對(duì)最外層的BufferedReader
使用use
即可)授帕。
對(duì)于按行讀取文件的功能,kotlin已經(jīng)提供了相應(yīng)的擴(kuò)展方法浮梢,該方法也是基于use
實(shí)現(xiàn)的自動(dòng)關(guān)閉功能跛十,直接調(diào)用該方法即可。
File("a.txt").readLines().forEach {
LogUtil.print(it)
}
3. 總結(jié)
kotlin的各種操作符基本上都是通過(guò)擴(kuò)展方法和內(nèi)聯(lián)函數(shù)實(shí)現(xiàn)的秕硝,這些操作符都是為了方便代碼開(kāi)發(fā)而添加的芥映,隨著kotlin越來(lái)越成熟,方便開(kāi)發(fā)人員使用的操作符也會(huì)越來(lái)越多,單純靠總結(jié)現(xiàn)有的操作符是無(wú)法全部掌握的奈偏,如果遇到不了解的操作符坞嘀,可以進(jìn)入操作符的方法中,查看一下源碼的實(shí)現(xiàn)方式惊来,再配合注釋就可以輕松使用大部分操作符了丽涩。
參考文章
Kotlin系列之let、with裁蚁、run内狸、apply、also函數(shù)的使用@熊喵先生