前言
在Kotlin介紹:第一部分虹茶,我們介紹了基本語法旗芬,現(xiàn)在我們可以去看看實(shí)際上如何使用Kotlin。在這篇文章中续扔,我們將介紹collections和lambdas表達(dá)式攻臀,一些方便的擴(kuò)展函數(shù)(apply,let纱昧,run和with)刨啸,null safety(空安全),那下面咱就開始吧识脆。
1设联、Collections and Lambdas
那么Kotlin collections是什么呢?如果您熟悉Java8灼捂,您將會(huì)對(duì)這些collection方法(java流)和語法十分了解离例。然而,Kotlin提供了大部分你可能想得到的擴(kuò)展悉稠,讓我們一起來看看吧粘招。
listOf(1,2,3)
mutableListOf("a", "b", "c")
setOf(1,2,3)
mutableSetOf("a", "b", "c")
mapOf(1 to "a", 2 to "b", 3 to "c")
mutableMapOf("a" to 1, "b" to 2, "c" to 3)
這些是基礎(chǔ),Kotlin為您提供了方法來創(chuàng)建collections偎球,我在這兒列出了不可變和可變版本的List洒扎,Set和Map。Kotlin系列的編程除了默認(rèn)的不變性外衰絮,還來自于Kotlin stdlib的擴(kuò)展功能袍冷。如果您熟悉函數(shù)式編程,那么您將熟悉大部分功能猫牡。它們是一組輔助函數(shù)和更高級(jí)的輔助函數(shù)胡诗,可以為您的集合提供常用操作。有了這些擴(kuò)展函數(shù)(map淌友,flatMap煌恢,forEach,fold震庭,reduce瑰抵,filter,zip器联,...)很多操作完成起來就很方便二汛。
在我們使用它們之前,我們需要先說一下lambdas表達(dá)式拨拓。Kotlin標(biāo)準(zhǔn)庫的collection擴(kuò)展功能的優(yōu)點(diǎn)來自于易使用lambdas表達(dá)式肴颊,只需使用足夠的類型推理來保證編程安全。在Kotlin中有幾種方法來定義lambdas函數(shù)渣磷。
val aList = listOf(1,2,4)
aList.map { elem ->
elem + 1
} // 2,3,5
aList.filter { it != 1} // 2,4
fun folder(a: Int, b: Int) = a + b
aList.reduce(::folder) // 7
// 或者: aList.reduce { a, b -> folder(a, b) }
在第一個(gè)例子中婿着,我們定義了Kotlin lambdas的最常見用法。我們可以用角括號(hào)(->)來縮寫匿名函數(shù),我們可以改變lambdas參數(shù)的名稱(在這里我們省略了類型定義竟宋;我們可以從aList列表中看到它是一個(gè)Int)奥务,然后我們定義lambda體,不需要使用return語句袜硫,最后一行將被返回。
下一個(gè)例子進(jìn)一步說明挡篓,甚至可以省略參數(shù)定義婉陷。在Kotlin中,默認(rèn)情況下官研,一個(gè)參數(shù)lambdas會(huì)接收到一個(gè)名為it的參數(shù)名秽澳。沒有必要去命名它。請(qǐng)注意戏羽,如果過多的使用it担神,尤其在嵌套函數(shù)中,會(huì)導(dǎo)致代碼非呈蓟ǎ混亂妄讯!
最后一個(gè)向我們展示了幾個(gè)新的概念,首先是一個(gè)本地函數(shù)酷宵,我們引用了::一個(gè)雙匯語法亥贸,本地函數(shù)的樣式和作用類似于類或全局作用域函數(shù),但還有一個(gè)額外功能浇垦,它還能訪問與函數(shù)本身在同一范圍定義的變量炕置。引用本地函數(shù)的第二種方法我們將它稱為內(nèi)部lambda,就像注釋中顯示的那樣男韧。
正如你所看到的朴摊,Kotlin中的lambdas是以直截了當(dāng)?shù)姆绞蕉x的。它們?cè)谀拇a中也很明顯此虑,并使得高階函數(shù)的使用變得簡(jiǎn)單甚纲。關(guān)于Kotlin和lambdas的最好部分是類型推斷,當(dāng)類型不匹配時(shí)朦前,它就在你的代碼下面出現(xiàn)一條紅色的線贩疙。通過編譯器的這種幫助,您可以將精力放在業(yè)務(wù)邏輯上况既,而不是試圖找出循環(huán)應(yīng)該遍歷多少遍这溅。
有關(guān)Kotlin的collection擴(kuò)展功能的更多信息可以在官方網(wǎng)站API doc中找到
2、Null safety(空安全)
當(dāng)涉及到可空性棒仍,Kotlin編譯器會(huì)非常嚴(yán)格的剖析您的代碼悲靴。如果定義一個(gè)可能為null的變量,則需要將其定義為可空莫其。那這該怎么寫呢癞尚?
var nil: String? = null
val notNil: String = "Hi"
var nil = null
這三個(gè)變量聲明有兩個(gè)可空值耸三,一個(gè)不為null。無效性的共同點(diǎn)是問號(hào)浇揩;可空變量和函數(shù)參數(shù)用問號(hào)定義仪壮。這個(gè)問號(hào)在Kotlin的null safe起著重要的作用。如果Kotlin編譯器在變量聲明或函數(shù)參數(shù)/返回類型中看到這個(gè)問號(hào)胳徽,它將強(qiáng)制您對(duì)空檢查积锅。如果您主要編寫的是Kotlin代碼,那您將會(huì)從NullPointException解放出來养盗。然而Kotlin與Java高度互操作缚陷,當(dāng)你傳入的數(shù)據(jù)可能為空時(shí)。Kotlin會(huì)讓你處理這個(gè)十億美元的錯(cuò)誤往核。
data class Lad(val name: String, val age: Int)
fun doSomething(laddy: Lad?){
print(laddy.name)
}
如果您嘗試這么做箫爷,Kotlin會(huì)編譯器將會(huì)給出提示。在android studio中聂儒,您將得到文本下方的紅色波浪線虎锚,它會(huì)給出Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Lad?。為了解決這個(gè)問題衩婚,你別無選擇翁都。
fun doSomething(laddy: Lad?){
if(laddy != null){
print(laddy.name)
}
}
fun doSomething(laddy: Lad?){
print(laddy?.name)
}
fun doSomething(laddy: Lad?){
laddy?.name?.let {
print(it)
}
/** 或者
* laddy?.name?.let { name ->
* print(name)
* }
**/
}
第一個(gè)例子是之前的寫法,正確的非空檢查谅猾。編譯器知道员淫,在完成null檢查之后迅耘,就可以使用我們的變量瞎颗,紅色波浪線就會(huì)從print語句中消失婉徘。在第二個(gè)例子,我們熟悉的問號(hào)再次出現(xiàn)了敬矩,但是這一次擔(dān)任是不同的角色概行。在這方面,問號(hào)會(huì)提示If laddy is not null, then take the name property from it弧岳。如果laddy為空凳忙,那么null將會(huì)打印到控制臺(tái)。
第三個(gè)介紹了一個(gè)擴(kuò)展功能禽炬,我們可以用它來調(diào)用let涧卵。如果我們想從我們的函數(shù)返回一些東西,我們可以使用elvis作為默認(rèn)值腹尖,以防我們碰到一個(gè)null柳恐。使用elvis有點(diǎn)像這樣:
fun doSomething(laddy: Lad?) = laddy?.name?: "James"
當(dāng)laddy和name都不為空時(shí),才會(huì)返回“James”。
3乐设、擴(kuò)展功能(Apply, Let, Run, and With)
Kotlin推出了一些擴(kuò)展功能讼庇,可以幫助我們處理。我們看到的第一個(gè)let是一個(gè)擴(kuò)展近尚,它將一個(gè)lambda作為參數(shù)蠕啄。在上面的例子中,it意味著我們的對(duì)象屬性name戈锻,但僅當(dāng)laddy和name不為空時(shí)有效歼跟。let只對(duì)存在的東西有用,作為擴(kuò)展功能舶沛,它不能擴(kuò)展不存在的東西。
Apply是另一個(gè)時(shí)髦的擴(kuò)展功能窗价,我們可以在很多情況下使用它如庭,一個(gè)常見的用法的就是創(chuàng)建一個(gè)需要許多調(diào)用的對(duì)象,但是沒有很好的方法來做到這一點(diǎn)撼港。為了簡(jiǎn)單起見坪它,我們能想到JavaBean及其getter和seeter。
public class JavaBeanClass {
private String thing;
private String thang;
public String getThing() {
return thing;
}
public void setThing(String thing) {
this.thing = thing;
}
public String getThang() {
return thang;
}
public void setThang(String thang) {
this.thang = thang;
}
}
這看起來有點(diǎn)繁瑣帝牡,沒關(guān)系往毡,讓我們使用Kotlin看看。
val mrBean = JavaBeanClass().apply {
setThing("Wild")
setThang("erbeest")
}
這就很舒服了靶溜,其實(shí)在Kotlin中开瞭,還可以有其它的寫法,與上述相同的代碼還可以這么寫:
val mrBean = JavaBeanClass().apply {
thing = "Wild"
thang = "erbeest"
}
這樣就更簡(jiǎn)潔了罩息。
接下來我們介紹with嗤详,這個(gè)家伙類似apply,實(shí)際上它不是一個(gè)擴(kuò)展函數(shù)瓷炮,它只是一個(gè)函數(shù)葱色,接受了兩個(gè)參數(shù)。我們來看一個(gè)例子娘香,我們將使用與mrBean之前定義的相同的方法苍狰。
with(mrBean) {
thing = "the"
thang = "ain't no"
}
和apply非常相似,你不覺得嗎烘绽?其實(shí)根本不一樣淋昭,那是因?yàn)槲覀儧]有做任何事,with返回with塊中最后一個(gè)表達(dá)式的值安接。這是一個(gè)重要的區(qū)別响牛,所以讓我們看一個(gè)更好的例子。
val yo = with(mrBean) {
thang + "thing"
}
print(yo) // ain't nothing
我們繼續(xù)看下一個(gè)操作符run,這是一個(gè)很簡(jiǎn)單的小東西呀打。它是一個(gè)擴(kuò)展函數(shù)矢赁,它接受一個(gè)參數(shù),一個(gè)lambda贬丛。它只是調(diào)用該lambda并返回該lambda的響應(yīng)撩银。“那么這個(gè)家伙有什么用呢豺憔?” “你可能會(huì)問”额获。使用它來運(yùn)行某些東西,當(dāng)且僅當(dāng)它被調(diào)用的對(duì)象不是null(使用它類似于let上面的幾行恭应,但在run這種情況下this作為范圍的對(duì)象)或使用它來調(diào)用我們的函數(shù)調(diào)用并保護(hù)我們的lambdas抄邀。我們必須記住,做run同樣的事情昼榛,但with通常更容易使用境肾。
4、類型: Checking, casting, and safety(檢查胆屿,轉(zhuǎn)換奥喻,安全)
在Java世界中,您可能會(huì)遇到這樣的if檢查if (clazz instanceOf SomeClass)程序員希望看到他們是否正確實(shí)現(xiàn)其接口或擴(kuò)展的基類非迹。
在Kotlin中類型推斷是非常好的环鲤,編譯器在編寫代碼時(shí)給出了很多有用的提示。當(dāng)您需要檢查對(duì)象是否是某種類型時(shí)憎兽,您可以使用is關(guān)鍵字冷离。
fun tryAndFailToCompileToGetTheAnswer(plzPassInThirteen: Any): Int {
return plzPassInThirteen + 29
}
fun getTheAnswer(plzPassInThirteen: Any): Int {
if (plzPassInThirteen is Int) {
return plzPassInThirteen + 29
}
return 666
}
println(getTheAnswer(13)) // 42
在上面的代碼塊中,第一個(gè)函數(shù)將會(huì)失敗纯命,并且根本沒有實(shí)際編譯酒朵,它會(huì)報(bào)錯(cuò),找不到類型匹配扎附。第二個(gè)功能修復(fù)了:它做了一個(gè)簡(jiǎn)單的is檢查蔫耽,在這一點(diǎn)上,Kotlin智能的將該值轉(zhuǎn)換為Int留夜,因此它可以在if語句中使用匙铡。通常當(dāng)when和is配合使用時(shí),您可以這么寫:
fun getTheAnswer(plzPassInThirteen: Any): Int = when(plzPassInThirteen) {
is Int -> plzPassInThirteen + 29
else -> 666
}
println(getTheAnswer(13)) // 42
這個(gè)例子與以前看到的if語句是一樣的碍粥,但這不是更美觀嗎鳖眼?
現(xiàn)在我們接觸了is和when在一起,現(xiàn)在我們可以繞個(gè)彎子談一談sealed classes嚼摩,Kotlin有一個(gè)sealed classes的概念钦讳,我們可以把它當(dāng)成一些子類的包裝矿瘦。
sealed class Seal
class SeaLion: Seal()
class Walrus: Seal()
class KissFromARose(val film: String): Seal()
如果我們有這樣的結(jié)構(gòu),一個(gè)密封的超類和三個(gè)繼承的子類愿卒,我們可以很好的處理多態(tài)和when以及is的組合缚去。
fun getTheAnswer(songOrAnimal: Seal): Unit = when(songOrAnimal) {
is SeaLion -> println("Animal")
is Walrus -> println("Song by Beatles")
}
這是編譯不過去的,編譯器會(huì)告訴我們when中的聲明少了哪一個(gè)子類琼开,如果我們將KissFromARose添加上就不會(huì)出現(xiàn)問題易结。
fun getTheAnswer(songOrAnimal: Seal): Unit = when(songOrAnimal) {
is SeaLion -> println("Animal")
is Walrus -> println("Song by Beatles")
is KissFromARose -> ("Heidi Klum")
}
println(getTheAnswer(Walrus())) // Song by Beatles
上面的編譯就沒什么問題,
有時(shí)候我們需要類型的轉(zhuǎn)換柜候,在Kotlin中搞动,使用as關(guān)鍵字。當(dāng)它被賦值時(shí)渣刷,我們可以假設(shè)它被轉(zhuǎn)換為該類型鹦肿,
val possiblyString: Any = "definitely"
possiblyString.capitalize()
上面的例子是無法編譯的,capitalize()會(huì)有錯(cuò)誤下劃線辅柴,編譯器告訴我們有一個(gè)Unresolved reference和resolver type mismatch箩溃。這個(gè)提示是對(duì)的,我們知道Any沒有capitalize()方法碌识,修改這個(gè)是容易的碾篡,我們只要將變量變成String就沒問題了虱而。
val possiblyString: Any = "definitely"
possiblyString as String
possiblyString.capitalize()
現(xiàn)在我們已經(jīng)了解了Kotlin的集合筏餐,空安全,類型安全牡拇,到這里第二部分的內(nèi)容也算是告一段落了魁瞪。