前言
在Kotlin介紹:第一部分,我們介紹了基本語法,現(xiàn)在我們可以去看看實(shí)際上如何使用Kotlin。在這篇文章中,我們將介紹collections和lambdas表達(dá)式,一些方便的擴(kuò)展函數(shù)(apply,let,run和with),null safety(空安全),那下面咱就開始吧。
1、Collections and Lambdas
那么Kotlin collections是什么呢?如果您熟悉Java8,您將會對這些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ù)和更高級的輔助函數(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) }
在第一個例子中,我們定義了Kotlin lambdas的最常見用法。我們可以用角括號(->)來縮寫匿名函數(shù),我們可以改變lambdas參數(shù)的名稱(在這里我們省略了類型定義;我們可以從aList列表中看到它是一個Int),然后我們定義lambda體,不需要使用return語句,最后一行將被返回。
下一個例子進(jìn)一步說明,甚至可以省略參數(shù)定義。在Kotlin中,默認(rèn)情況下,一個參數(shù)lambdas會接收到一個名為it的參數(shù)名。沒有必要去命名它。請注意,如果過多的使用it,尤其在嵌套函數(shù)中,會導(dǎo)致代碼非?;靵y!
最后一個向我們展示了幾個新的概念,首先是一個本地函數(shù),我們引用了::一個雙匯語法,本地函數(shù)的樣式和作用類似于類或全局作用域函數(shù),但還有一個額外功能,它還能訪問與函數(shù)本身在同一范圍定義的變量。引用本地函數(shù)的第二種方法我們將它稱為內(nèi)部lambda,就像注釋中顯示的那樣。
正如你所看到的,Kotlin中的lambdas是以直截了當(dāng)?shù)姆绞蕉x的。它們在您的代碼中也很明顯,并使得高階函數(shù)的使用變得簡單。關(guān)于Kotlin和lambdas的最好部分是類型推斷,當(dāng)類型不匹配時,它就在你的代碼下面出現(xiàn)一條紅色的線。通過編譯器的這種幫助,您可以將精力放在業(yè)務(wù)邏輯上,而不是試圖找出循環(huán)應(yīng)該遍歷多少遍。
有關(guān)Kotlin的collection擴(kuò)展功能的更多信息可以在官方網(wǎng)站API doc中找到
2、Null safety(空安全)
當(dāng)涉及到可空性,Kotlin編譯器會非常嚴(yán)格的剖析您的代碼。如果定義一個可能為null的變量,則需要將其定義為可空。那這該怎么寫呢?
var nil: String? = null
val notNil: String = "Hi"
var nil = null
這三個變量聲明有兩個可空值,一個不為null。無效性的共同點(diǎn)是問號;可空變量和函數(shù)參數(shù)用問號定義。這個問號在Kotlin的null safe起著重要的作用。如果Kotlin編譯器在變量聲明或函數(shù)參數(shù)/返回類型中看到這個問號,它將強(qiáng)制您對空檢查。如果您主要編寫的是Kotlin代碼,那您將會從NullPointException解放出來。然而Kotlin與Java高度互操作,當(dāng)你傳入的數(shù)據(jù)可能為空時。Kotlin會讓你處理這個十億美元的錯誤。
data class Lad(val name: String, val age: Int)
fun doSomething(laddy: Lad?){
print(laddy.name)
}
如果您嘗試這么做,Kotlin會編譯器將會給出提示。在android studio中,您將得到文本下方的紅色波浪線,它會給出Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Lad?。為了解決這個問題,你別無選擇。
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)
* }
**/
}
第一個例子是之前的寫法,正確的非空檢查。編譯器知道,在完成null檢查之后,就可以使用我們的變量,紅色波浪線就會從print語句中消失。在第二個例子,我們熟悉的問號再次出現(xiàn)了,但是這一次擔(dān)任是不同的角色。在這方面,問號會提示If laddy is not null, then take the name property from it。如果laddy為空,那么null將會打印到控制臺。
第三個介紹了一個擴(kuò)展功能,我們可以用它來調(diào)用let。如果我們想從我們的函數(shù)返回一些東西,我們可以使用elvis作為默認(rèn)值,以防我們碰到一個null。使用elvis有點(diǎn)像這樣:
fun doSomething(laddy: Lad?) = laddy?.name?: "James"
當(dāng)laddy和name都不為空時,才會返回“James”。
3、擴(kuò)展功能(Apply, Let, Run, and With)
Kotlin推出了一些擴(kuò)展功能,可以幫助我們處理。我們看到的第一個let是一個擴(kuò)展,它將一個lambda作為參數(shù)。在上面的例子中,it意味著我們的對象屬性name,但僅當(dāng)laddy和name不為空時有效。let只對存在的東西有用,作為擴(kuò)展功能,它不能擴(kuò)展不存在的東西。
Apply是另一個時髦的擴(kuò)展功能,我們可以在很多情況下使用它,一個常見的用法的就是創(chuàng)建一個需要許多調(diào)用的對象,但是沒有很好的方法來做到這一點(diǎ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"
}
這樣就更簡潔了。
接下來我們介紹with,這個家伙類似apply,實(shí)際上它不是一個擴(kuò)展函數(shù),它只是一個函數(shù),接受了兩個參數(shù)。我們來看一個例子,我們將使用與mrBean之前定義的相同的方法。
with(mrBean) {
thing = "the"
thang = "ain't no"
}
和apply非常相似,你不覺得嗎?其實(shí)根本不一樣,那是因?yàn)槲覀儧]有做任何事,with返回with塊中最后一個表達(dá)式的值。這是一個重要的區(qū)別,所以讓我們看一個更好的例子。
val yo = with(mrBean) {
thang + "thing"
}
print(yo) // ain't nothing
我們繼續(xù)看下一個操作符run,這是一個很簡單的小東西。它是一個擴(kuò)展函數(shù),它接受一個參數(shù),一個lambda。它只是調(diào)用該lambda并返回該lambda的響應(yīng)。“那么這個家伙有什么用呢?” “你可能會問”。使用它來運(yùn)行某些東西,當(dāng)且僅當(dāng)它被調(diào)用的對象不是null(使用它類似于let上面的幾行,但在run這種情況下this作為范圍的對象)或使用它來調(diào)用我們的函數(shù)調(diào)用并保護(hù)我們的lambdas。我們必須記住,做run同樣的事情,但with通常更容易使用。
4、類型: Checking, casting, and safety(檢查,轉(zhuǎn)換,安全)
在Java世界中,您可能會遇到這樣的if檢查if (clazz instanceOf SomeClass)程序員希望看到他們是否正確實(shí)現(xiàn)其接口或擴(kuò)展的基類。
在Kotlin中類型推斷是非常好的,編譯器在編寫代碼時給出了很多有用的提示。當(dāng)您需要檢查對象是否是某種類型時,您可以使用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
在上面的代碼塊中,第一個函數(shù)將會失敗,并且根本沒有實(shí)際編譯,它會報錯,找不到類型匹配。第二個功能修復(fù)了:它做了一個簡單的is檢查,在這一點(diǎn)上,Kotlin智能的將該值轉(zhuǎn)換為Int,因此它可以在if語句中使用。通常當(dāng)when和is配合使用時,您可以這么寫:
fun getTheAnswer(plzPassInThirteen: Any): Int = when(plzPassInThirteen) {
is Int -> plzPassInThirteen + 29
else -> 666
}
println(getTheAnswer(13)) // 42
這個例子與以前看到的if語句是一樣的,但這不是更美觀嗎?
現(xiàn)在我們接觸了is和when在一起,現(xiàn)在我們可以繞個彎子談一談sealed classes,Kotlin有一個sealed classes的概念,我們可以把它當(dāng)成一些子類的包裝。
sealed class Seal
class SeaLion: Seal()
class Walrus: Seal()
class KissFromARose(val film: String): Seal()
如果我們有這樣的結(jié)構(gòu),一個密封的超類和三個繼承的子類,我們可以很好的處理多態(tài)和when以及is的組合。
fun getTheAnswer(songOrAnimal: Seal): Unit = when(songOrAnimal) {
is SeaLion -> println("Animal")
is Walrus -> println("Song by Beatles")
}
這是編譯不過去的,編譯器會告訴我們when中的聲明少了哪一個子類,如果我們將KissFromARose添加上就不會出現(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
上面的編譯就沒什么問題,
有時候我們需要類型的轉(zhuǎn)換,在Kotlin中,使用as關(guān)鍵字。當(dāng)它被賦值時,我們可以假設(shè)它被轉(zhuǎn)換為該類型,
val possiblyString: Any = "definitely"
possiblyString.capitalize()
上面的例子是無法編譯的,capitalize()會有錯誤下劃線,編譯器告訴我們有一個Unresolved reference和resolver type mismatch。這個提示是對的,我們知道Any沒有capitalize()方法,修改這個是容易的,我們只要將變量變成String就沒問題了。
val possiblyString: Any = "definitely"
possiblyString as String
possiblyString.capitalize()
現(xiàn)在我們已經(jīng)了解了Kotlin的集合,空安全,類型安全,到這里第二部分的內(nèi)容也算是告一段落了。