Kotlin語法手冊(一)
在使用kotlin時壮吩,由于掌握的不夠牢靠蟆技,好多時候也還是Java編程的習(xí)慣,浪費了kotlin提供的語言特性沿侈,方便性闯第,間接性,在閱讀一些Android開源庫的時候缀拭,由于好多都是kotlin語法編寫的咳短,看的比較費勁,還得去查閱kotlin的語法蛛淋,比較不方便咙好,故把kotlin的語法記錄下來,方便查閱溫故褐荷,鞏固自己的基礎(chǔ)知識勾效。
變量
kotlin 中,變量分為 可變變量(var) 和 不可變變量(val) 兩類叛甫。
- val:不可變引用层宫,對應(yīng)的是 Java 中的 final 變量;使用 val 聲明的變量不能在初始化之后再次賦值其监。
- var:可變引用萌腿,對應(yīng)的是 Java 中的非 final 變量;使用 var 聲明的變量的值可以被改變抖苦。
不可變變量在賦值之后就不能再去改變它的狀態(tài)了毁菱,因此不可變變量可以說是線程安全的,因為它們無法改變锌历,所有線程訪問到的對象都是同一個贮庞,因此也不需要去做訪問控制。開發(fā)者應(yīng)當(dāng)盡可能地使用不可變變量究西,這樣可以讓代碼更加接近函數(shù)式編程風(fēng)格贸伐。
fun main() {
//聲明一個整數(shù)類型的不可變變量,初始化后intValue的值不能再改變了
val intValue: Int = 100
//聲明一個雙精度類型的可變變量
var doubleValue: Double = 100.0
}
在聲明變量時怔揩,通常不需要顯式指明變量的類型捉邢,可以由編譯器根據(jù)上下文自動推導(dǎo)出來。如果只讀變量在聲明時沒有初始的默認(rèn)值商膊,則必須指明變量類型伏伐,且在使用前必須確保在各個分支條件下變量可以被初始化,否則編譯器就會報異常晕拆。
數(shù)據(jù)類型
基本數(shù)據(jù)類型
在 kotlin中藐翎,一切都是對象材蹬,可以在任何變量上調(diào)用其成員函數(shù)和屬性,并不區(qū)分基本數(shù)據(jù)類型和它的包裝類吝镣。也就是說kotlin 沒有像 Java 中那樣的原始基本類型堤器,但 byte、char末贾、integer闸溃、float 或者 boolean 等類型仍然有保留,但是全部都作為對象存在拱撵。
//在 kotlin 中辉川,int、long拴测、float 等類型仍然存在乓旗,但是是作為對象存在的
val intIndex: Int = 100
//等價于,編譯器會自動進(jìn)行類型推導(dǎo)
val intIndex = 100
//數(shù)字類型不會自動轉(zhuǎn)型集索,必須要進(jìn)行明確的類型轉(zhuǎn)換
val doubleIndex: Double = intIndex.toDouble()
//以下代碼會提示錯誤屿愚,需要進(jìn)行明確的類型轉(zhuǎn)換
//val doubleIndex: Double = intIndex
val intValue: Int = 1
val longValue: Long = 1
//以下代碼會提示錯誤,因為兩者的數(shù)據(jù)類型不一致务荆,需要轉(zhuǎn)換為同一類型后才能進(jìn)行比較
//println(intValue == longValue)
//Char 不能直接作為數(shù)字來處理渺鹦,需要主動轉(zhuǎn)換
val ch: Char = 'c'
val charValue: Int = ch.toInt()
//以下代碼會提示錯誤
//val charValue: Int = ch
//不支持八進(jìn)制
//二進(jìn)制
val value1 = 0b00101
//十六進(jìn)制
val value2 = 0x123
字符串
字符串用 String
類型表示。字符串是不可變的蛹含。字符串的元素:使用索引運算符訪問: s[i]
;可以用 for 循環(huán)迭代字符串,也可以用 + 來連接字符串塞颁。
val str = "hello"
println(str[1])
for (c in str) {
println(c)
}
val str1 = str + " world"
kotlin 支持在字符串字面值中引用局部變量浦箱,只需要在變量名前加上字符 $ 即可,此外還可以包含用花括號{}括起來的表達(dá)式祠锣,此時會自動求值并把結(jié)果合并到字符串中酷窥。
val intValue = 100
//可以直接包含變量
println("intValue value is $intValue") //intValue value is 100
//也可以包含表達(dá)式
println("(intValue + 100) value is ${intValue + 100}") //(intValue + 100) value is 200
如果你需要在原始字符串中表示字面值($)字符(它不支持反斜杠轉(zhuǎn)義),可以用下列語法:
val price = "${'$'}100.99"
println(price) //$100.99
數(shù)組
數(shù)組在 Kotlin 中使用 Array
類來表示伴网,它定義了 get
與 set
函數(shù)(按照運算符重載約定這會轉(zhuǎn)變?yōu)?[]
)以及 size
屬性蓬推,以及一些其他有用的成員函數(shù):
-
arrayOf():使用庫函數(shù) arrayOf() 來創(chuàng)建一個數(shù)組并傳遞元素值給它,如:
arrayOf(1, 2, 3)
就創(chuàng)建了[1, 2, 3]
澡腾。 -
arrayOfNulls:庫函數(shù)
arrayOfNulls()
可以用于創(chuàng)建一個指定大小的沸伏、初始化所有元素都為空的數(shù)組。 - Array:Array是接受數(shù)組大小以及一個函數(shù)參數(shù)的構(gòu)造函數(shù)动分,用作參數(shù)的函數(shù)能夠返回給定索引的每個元素初始值毅糟,如下所示:
// 創(chuàng)建一個 Array<String> 數(shù)組大小為5,函數(shù)為(i * i).toString()的字符串?dāng)?shù)組
val asc = Array(5) { i -> (i * i).toString() }//["0", "1", "4", "9", "16"]
asc.forEach { println(it) }
基本數(shù)據(jù)類型數(shù)組
數(shù)組類型的類型參數(shù)如上面的Array<String>澜公,始終會變成對象類型姆另,因此聲明 Array< Int > 將是一個包含裝箱類型(java.lang.Integer)的數(shù)組,如果想要創(chuàng)建沒有裝箱的基本數(shù)據(jù)類型的數(shù)組,必須使用一個基本數(shù)據(jù)類型數(shù)組的特殊類IntArray迹辐、ByteArray蝶防、BooleanArray 等,這些類與 Array
并沒有繼承關(guān)系明吩,但是它們有同樣的方法屬性集间学,它們也都有相應(yīng)的工廠方法。
val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
// 大小為 5贺喝、值為 [0, 0, 0, 0, 0] 的整型數(shù)組
val arr = IntArray(5)
// 例如:用常量初始化數(shù)組中的值
// 大小為 5菱鸥、值為 [42, 42, 42, 42, 42] 的整型數(shù)組
val arr = IntArray(5) { 42 }
// 例如:使用 lambda 表達(dá)式初始化數(shù)組中的值
// 大小為 5、值為 [0, 1, 2, 3, 4] 的整型數(shù)組(值初始化為其索引值)
var arr = IntArray(5) { it * 1 }
集合
kotlin中集合分為只讀集合與可變集合躏鱼,如下所示:
集合元素 | 只讀 | 可變 |
---|---|---|
List | listOf | mutableListOf氮采、arrayListOf |
Set | setOf | mutableSetOf、hashSetOf染苛、linkedSetOf鹊漠、sortedSetOf |
Map | mapOf | mutableMapOf、hashMapOf茶行、linkedMapOf躯概、sortedMapOf |
- List是一個有序集合,可通過索引訪問元素畔师。元素可以在 list 中出現(xiàn)多次娶靡。
- Set是唯一元素的集合,一組無重復(fù)的對象看锉。一般來說 set 中元素的順序并不重要姿锭。例如,字母表是字母的集合(set)伯铣。
- Map是一組鍵值對呻此。鍵是唯一的,每個鍵都剛好映射到一個值腔寡。值可以重復(fù)
只讀集合的可變性
只讀集合不一定就是不可變的焚鲜。例如,假設(shè)存在一個擁有只讀類型接口的對象放前,該對象存在兩個不同的引用忿磅,一個只讀,一個可變凭语,當(dāng)可變引用修改了該對象后贝乎,這對只讀引用來說就相當(dāng)于“只讀集合被修改了”,因此只讀集合并不總是線程安全的叽粹。如果需要在多線程環(huán)境下處理數(shù)據(jù)览效,需要保證正確地同步了對數(shù)據(jù)的訪問却舀,或者使用支持并發(fā)訪問的數(shù)據(jù)結(jié)構(gòu)
例如,list1 和 list1 引用到同一個集合對象锤灿, list3 對集合的修改同時會影響到 list1
val list1: List<String> = JavaMain.names
val list3: MutableList<String> = JavaMain.names
list1.forEach { it -> println(it) } //leavesC Ye
list3.forEach { it -> println(it) } //leavesC Ye
for (index in list3.indices) {
list3[index] = list3[index].toUpperCase()
}
list1.forEach { it -> println(it) } //LEAVESC YE
集合與可空性
集合的可空性可以分為三種:
- 可以包含為 null 的集合元素
- 集合本身可以為 null
- 集合本身可以為 null挽拔,且可以包含為 null 的集合元素
例如,intList1 可以包含為 null 的集合元素但校,但集合本身不能指向 null螃诅;intList2 不可以包含為 null 的集合元素,但集合本身可以指向 null状囱;intList3 可以包含為 null 的集合元素术裸,且集合本身能指向 null
//List<Int?> 是能持有 Int? 類型值的列表
val intList1: List<Int?> = listOf(10, 20, 30, 40, null)
//List<Int>? 是可以為 null 的列表
var intList2: List<Int>? = listOf(10, 20, 30, 40)
intList2 = null
//List<Int?>? 是可以為 null 的列表,且能持有 Int? 類型值
var intList3: List<Int?>? = listOf(10, 20, 30, 40, null)
intList3 = null
其他數(shù)據(jù)類型
-
Any:Any 類型是 kotlin 所有非空類型的超類型亭枷,包括像 Int 這樣的基本數(shù)據(jù)類型袭艺,如果把基本數(shù)據(jù)類型的值賦給 Any 類型的變量,則會自動裝箱為
java.lang.Integer
的叨粘。 - Any?:Any?類型是 kotlin 所有可空類型的超類型猾编,如果想要使變量可以存儲包括 null 在內(nèi)的所有可能的值,則需要使用 Any?
- Unit:Unit 類型類似于 Java 中的 void升敲,可以用于函數(shù)沒有返回值時的情況答倡,如果函數(shù)返回值為 Unit,則可以省略該聲明驴党,Unit也可以作為類型參數(shù)來聲明變量瘪撇。
- Nothing:Nothing 類型沒有任何值,只有被當(dāng)做函數(shù)返回值使用港庄,或者被當(dāng)做泛型函數(shù)返回值的類型參數(shù)使用時才會有意義
函數(shù)
kotlin 中的函數(shù)以關(guān)鍵字 fun 作為開頭倔既,函數(shù)名稱緊隨其后,再之后是用括號包裹起來的參數(shù)列表攘轩,如果函數(shù)有返回值,則再加上返回值類型码俩,用一個冒號與參數(shù)列表隔開度帮。
//fun 用于表示聲明一個函數(shù),double 是函數(shù)名稿存,x表示傳入?yún)?shù)笨篷,Int 表示函數(shù)的返回值類型是int型
fun double(x: Int): Int {
return 2 * x
}
還有一種是表達(dá)式函數(shù)體,它是用單行表達(dá)式與等號定義的函數(shù)瓣履,表達(dá)式函數(shù)體的返回值類型可以省略率翅,返回值類型可以自動推斷的。如:fun double(x: Int) = x * 2
如果函數(shù)沒有有意義的返回值袖迎,則可以聲明為 Unit 冕臭,也可以省略 Unit腺晾,下面的三種寫法是等價的:
fun test(str: String, int: Int): Unit {
println(str.length + int)
}
fun test(str: String, int: Int) {
println(str.length + int)
}
fun test(str: String, int: Int) = println(str.length + int)
函數(shù)的參數(shù)
命名參數(shù)
kotlin 允許我們使用命名參數(shù),即在調(diào)用某函數(shù)的時候辜贵,可以將函數(shù)參數(shù)名一起標(biāo)明悯蝉,從而明確地表達(dá)該參數(shù)的含義與作用,但是在指定了一個參數(shù)的名稱后托慨,之后的所有參數(shù)都需要標(biāo)明名稱鼻由。如下所示:
fun main() {
//該寫法是錯誤的,在指定了一個參數(shù)的名稱后厚棵,之后的所有參數(shù)都需要標(biāo)明名稱
compute(index = 110, "hello")
compute(index = 120, value = "hello")
compute(130, value = "hello")
}
fun compute(index: Int, value: String) {
}
默認(rèn)參數(shù)
函數(shù)參數(shù)可以有默認(rèn)值蕉世,當(dāng)省略相應(yīng)的參數(shù)時使用默認(rèn)值。與其他語言相比婆硬,這可以減少重載數(shù)量狠轻。
fun main() {
compute(24)
compute(24, "world")
}
fun compute(age: Int, name: String = "hello") {
}
有默認(rèn)參數(shù)時,可以省略的只有排在末尾的參數(shù)柿祈,其他位置的是不能省略的哈误,如下所示:
fun main() {
//錯誤,不能省略參數(shù) name
// compute(24)
// compute(24,100)
//可以省略參數(shù) value
compute("hello", 24)
}
fun compute(name: String = "hello", age: Int, value: Int = 100) {}
但是躏嚎,如果使用命名參數(shù)蜜自,可以省略任何有默認(rèn)值的參數(shù),而且也可以按照任意順序傳入需要的參數(shù)卢佣。
fun main() {
compute(age = 24)
compute(age = 24, name = "hello")
compute(age = 24, value = 90, name = "hello")
compute(value = 90, age = 24, name = "hello")
}
fun compute(name: String = "hello", age: Int, value: Int = 100) {
}
可變參數(shù)
kotlin 的語法與 Java 有所不同重荠,可變參數(shù)改為通過使用 varage 關(guān)鍵字聲明
fun main() {
compute()
compute("hello")
compute("hello", "world")
compute("hello", "world", "kotlin")
}
fun compute(vararg name: String) {
name.forEach { println(it) }
}
在 Java 中,可以直接將數(shù)組傳遞給可變參數(shù)虚茶,而 kotlin 要求顯式地解包數(shù)組戈鲁,以便每個數(shù)組元素在函數(shù)中能夠作為單獨的參數(shù)來調(diào)用,這個功能被稱為展開運算符嘹叫,使用方式就是在數(shù)組參數(shù)前加一個 *
fun main() {
val names = arrayOf("hello", "world", "kotlin")
compute(* names)
}
fun compute(vararg name: String) {
name.forEach { println(it) }
}
局部函數(shù)
在Kotlin中婆殿,支持在函數(shù)中嵌套函數(shù),被嵌套的函數(shù)稱為局部函數(shù)
fun compute(name: String, country: String) {
fun check(string: String) {
if (string.isEmpty()) {
throw IllegalArgumentException("參數(shù)錯誤")
}
}
check(name)
check(country)
}
check方法體是放在compute方法體中罩扇,check方法被稱為局部方法或局部函數(shù)婆芦;check只能在compute中方法調(diào)用,在compute方法外調(diào)用喂饥,會引起編譯錯誤消约。
控制流
IF表達(dá)式
在 Kotlin 中,if是一個表達(dá)式员帮,即它會返回一個值或粮。 因此就不需要三元運算符(條件 ? 然后 : 否則),因為普通的 if 就能勝任這個角色捞高,故kotlin中沒有三元運算符氯材。
// 傳統(tǒng)用法
var max = a
if (a < b) max = b
// With else
var max: Int
if (a > b) {
max = a
} else {
max = b
}
// 作為表達(dá)式
val max = if (a > b) a else b
if 的分支可以是代碼塊渣锦,最后的表達(dá)式作為該塊的值
//a,b就是該塊兒最后返回的值
val max = if (a > b) {
print("Choose a")
a
} else {
print("Choose b")
b
}
如果你使用 if 作為表達(dá)式而不是語句(例如:返回它的值或者把它賦給變量),該表達(dá)式需要有 else
分支
when表達(dá)式
when 表達(dá)式與 Java 中的 switch/case 類似浓体,但功能更為強(qiáng)大泡挺。when 既可以被當(dāng)做表達(dá)式使用也可以被當(dāng)做語句使用,when 將參數(shù)和所有的分支條件順序比較直到某個分支滿足條件命浴,然后它會運行右邊的表達(dá)式娄猫。
如果 when 被當(dāng)做表達(dá)式來使用,符合條件的分支的值就是整個表達(dá)式的值生闲。與 Java 的 switch/case 不同之處是 when 表達(dá)式的參數(shù)可以是任何類型媳溺,并且分支也可以是一個條件。
和 if 一樣碍讯,when 表達(dá)式每一個分支可以是一個代碼塊悬蔽,它的值是代碼塊中最后的表達(dá)式的值,如果其它分支都不滿足條件將會求值于 else 分支捉兴。
如果 when 作為一個表達(dá)式使用蝎困,則必須有 else 分支, 除非編譯器能夠檢測出所有的可能情況都已經(jīng)覆蓋了倍啥。如果很多分支需要用相同的方式處理禾乘,則可以把多個分支條件放在一起,用逗號分隔虽缕。
val value = 2
when (value) {
in 4..9 -> println("in 4..9") //區(qū)間判斷
3 -> println("value is 3") //相等性判斷
2, 6 -> println("value is 2 or 6") //多值相等性判斷
is Int -> println("is Int") //類型判斷
else -> println("else") //如果以上條件都不滿足始藕,則執(zhí)行 else
}
fun main() {
//返回 when 表達(dá)式
fun parser(obj: Any): String =
when (obj) {
1 -> "value is 1"
"4" -> "value is string 4"
is Long -> "value type is long"
else -> "unknown"
}
}
when 語句也可以不帶參數(shù)來使用
when {
1 > 5 -> println("1 > 5")
3 > 1 -> println("3 > 1")
}
For循壞
//和java中使用方式很類似
val list = listOf(1, 4, 10, 34, 10)
for (value in list) {
println(value)
}
通過索引來遍歷
val items = listOf("H", "e", "l", "l", "o")
//通過索引來遍歷List
for (index in items.indices) {
println("${index}對應(yīng)的值是:${items[index]}")
}
也可以在每次循環(huán)中獲取當(dāng)前索引和相應(yīng)的值
val list = listOf(1, 4, 10, 34, 10)
for ((index, value) in list.withIndex()) {
println("index : $index , value :$value")
}
也可以自定義循環(huán)區(qū)間
for (index in 2..10) {
println(index)
}
while循環(huán)和do/while循環(huán)
兩者的使用和Java中的使用相似。
val list = listOf(1, 4, 15, 2, 4, 10, 0, 9)
var index = 0
while (index < list.size) {
println(list[index])
index++
}
val list = listOf(1, 4, 15, 2, 4, 10, 0, 9)
var index = 0
do {
println(list[index])
index++
} while (index < list.size)
返回與跳轉(zhuǎn)
Kotlin 有三種結(jié)構(gòu)化跳轉(zhuǎn)表達(dá)式(作用和java語言中的類似):
- return 默認(rèn)從最直接包圍它的函數(shù)或者匿名函數(shù)返回
- break 終止最直接包圍它的循環(huán)
- continue 繼續(xù)下一次最直接包圍它的循環(huán)
在 kotlin 中任何表達(dá)式都可以用標(biāo)簽(label)來標(biāo)記氮趋,標(biāo)簽的格式為標(biāo)識符后跟 @ 符號伍派,例如:abc@ 、fooBar@ 都是有效的標(biāo)簽
fun main() {
fun1()
}
fun fun1() {
val list = listOf(1, 4, 6, 8, 12, 23, 40)
loop@ for (it in list) {
if (it == 8) {
continue
}
//當(dāng)值是23的時候剩胁,退出標(biāo)記的loop循環(huán)
if (it == 23) {
break@loop
}
println("value is $it")
}
println("function end")
}
通常情況下使用隱式標(biāo)簽更方便诉植, 隱式標(biāo)簽與接受該 lambda 的函數(shù)同名,return也可添加標(biāo)簽限制(如下:)
fun fun3() {
val list = listOf(1, 4, 6, 8, 12, 23, 40)
list.forEach {
if (it == 8) {
//這是就是在return上加了隱式標(biāo)簽forEach的限制昵观,使之只在forEach當(dāng)前循環(huán)中終止返回晾腔,效果同continue
return@forEach
}
println("value is $it")
}
println("function end")
//運行fun3方法的話,會輸出以下結(jié)果:
// value is 1
// value is 4
// value is 6
// value is 12
// value is 23
// value is 40
// function end
}
fun fun4() {
val list = listOf(1, 4, 6, 8, 12, 23, 40)
list.forEach loop@{
if (it == 8) {
return@loop//同fun3一樣索昂,這里是添加了loop的標(biāo)簽顯式標(biāo)簽
}
println("value is $it")
}
println("function end")
//運行fun4方法的話建车,會輸出以下結(jié)果:
// value is 1
// value is 4
// value is 6
// value is 12
// value is 23
// value is 40
// function end
}
fun fun5() {
listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
if (value == 3) {
//局部返回到匿名函數(shù)的調(diào)用者扩借,即 forEach 循環(huán)
return
}
println("value is $value")
})
println("function end")
}
//運行fun5方法的話椒惨,會輸出以下結(jié)果:
//value is 1
//value is 2
//value is 4
//value is 5
//function end
區(qū)間
Kotlin 可通過調(diào)用 kotlin.ranges
包中的 [rangeTo()
] 函數(shù)及其操作符 ..
形式的輕松地創(chuàng)建兩個值的區(qū)間。 通常潮罪,rangeTo()
會輔以 in
或 !in
函數(shù)
if (i in 1..4) { // 等同于 i>=1 && i <= 4
print(i)
}
if (i in 1.rangeTo(4)) { // 和上面是相同的
print(i)
}
數(shù)字類型的 ranges 在被迭代時康谆,等同于java中帶索引的fori的循環(huán)的效果
for (i in 1..4) {
print(i)
}
要反向迭代數(shù)字领斥,請使用 [downTo
]函數(shù)而不是 ..
或rangeTo(
)。
for (i in 4 downTo 1) print(i)
這是通過 [step
]函數(shù)完成任意步長(不一定為 1 )迭代數(shù)字
for (i in 1..8 step 2) print(i)
println()
for (i in 8 downTo 1 step 2) print(i)
以上聲明的都是閉區(qū)間沃暗,如果想聲明的是開區(qū)間)月洛,可以使用 until 函數(shù)
for (i in 1 until 4) {
println(i)
}
空安全
在 kotlin 中,類型系統(tǒng)將一個引用分為可以容納 null (可空引用)或者不能容納 null(非空引用)兩種類型孽锥。 常規(guī)的變量不能指向null,如果希望一個變量可以儲存 null 引用嚼黔,需要顯式地在類型名稱后面加上問號?
。
//name變量不能被賦值為null
var name: String = "hello"
//name變量可以被賦值為null
var name: String? = "hello"
kotlin 對可空類型的顯式支持有助于防止 NullPointerException 導(dǎo)致的異常問題惜辑,編譯器不允許不對可空變量做 null 檢查就直接調(diào)用其屬性唬涧。
var name: String? = "hello"
val l = name.length // 編譯器會報錯,因為name變量可能為null
在寫代碼的時候盛撑,我們可以對可空的變量做判空判斷碎节,kotlin也為我們提供了安全的調(diào)用的運算符
安全的調(diào)用
?.
:如果變量值非空,則變量的方法或?qū)傩詴徽{(diào)用抵卫,否則直接返回 null狮荔。
//b變量聲明的是可空類型的,當(dāng)b!=null的時候介粘,就返回的b.length的值
//當(dāng)b=null時殖氏,b?.length就自動返回的null,可以看出這個表達(dá)式返回的類型是Int?
println(b?.length)
安全調(diào)用在鏈?zhǔn)秸{(diào)用中也很有用,例如碗短,如果一個員工 Bob 可能會(或者不會)分配給一個部門受葛, 并且可能有另外一個員工是該部門的負(fù)責(zé)人,那么獲取 Bob 所在部門負(fù)責(zé)人(如果有的話)的名字偎谁,我們寫作:bob?.department?.head?.name
如果任意一個屬性(環(huán)節(jié))為空总滩,這個鏈?zhǔn)秸{(diào)用就會返回 null。
如果要只對非空值執(zhí)行某個操作巡雨,安全調(diào)用操作符可以與 [let
] 一起使用:
val listWithNulls: List<String?> = listOf("Kotlin", null)
for (item in listWithNulls) {
item?.let { println(it) } // 輸出 Kotlin 并忽略 null
}
安全調(diào)用也可以出現(xiàn)在賦值的左側(cè)闰渔。這樣,如果調(diào)用鏈中的任何一個接收者為空都會跳過賦值铐望,而右側(cè)的表達(dá)式根本不會求值:
// 如果 `person` 或者 `person.department` 其中之一為空冈涧,都不會調(diào)用該函數(shù):
person?.department?.head = managersPool.getManager()
Elvis 操作符
當(dāng)我們有一個可空的引用 b
時,如果 b
非空正蛙,我使用它督弓;如果使用 b
是空的話,我們想使用某個非空的值乒验,如下所示:
val l: Int = if (b != null) b.length else -1
除了可以用if表達(dá)式愚隧,這還可以通過 Elvis 操作符表達(dá),寫作 ?:
//當(dāng)表達(dá)式b?.length為空(即b=null)的時候賦值為-1
val l = b?.length ?: -1
如果 ?:
左側(cè)表達(dá)式非空锻全,elvis 操作符就返回其左側(cè)表達(dá)式狂塘,否則返回右側(cè)表達(dá)式录煤。 請注意,當(dāng)且僅當(dāng)左側(cè)為空時荞胡,才會對右側(cè)表達(dá)式求值妈踊。
安全的類型轉(zhuǎn)換
如果對象不是目標(biāo)類型,那么常規(guī)類型轉(zhuǎn)換可能會導(dǎo)致 ClassCastException
泪漂。 另一個選擇是使用安全的類型轉(zhuǎn)換as?
廊营,如果嘗試轉(zhuǎn)換不成功則返回 null:
//如果a不是Int類型的話,會返回null
val aInt: Int? = a as? Int
非空斷言運算符!!
!!
是將任何值轉(zhuǎn)換為非空類型萝勤,若該值為空則拋出異常
//如果b=null赘风,會拋出空指針NPE異常
val l = b!!.length
可空類型的擴(kuò)展
為可空類型定義擴(kuò)展函數(shù)是一種更強(qiáng)大的處理 null 值的方式,可以允許接收者為 null 的調(diào)用纵刘,并在該函數(shù)中處理 null 邀窃,而不是在確保變量不為 null 之后再調(diào)用它的方法
//可以被正常調(diào)用而不會發(fā)生空指針異常
val name: String? = null
println(name.isNullOrEmpty()) //true
類型檢測與類型轉(zhuǎn)換
類型檢查is
與 !is
操作符
在運行時通過使用 is
操作符或其否定形式 !is
來檢測對象是否符合給定類型
if (obj is String) {
print(obj.length)
}
if (obj !is String) { // 與 !(obj is String) 相同
print("Not a String")
}
else {
print(obj.length)
}
智能轉(zhuǎn)換
在許多情況下,不需要在 Kotlin 中使用顯式轉(zhuǎn)換操作符假哎,因為編譯器跟蹤不可變值的 is
-檢測以及[顯式轉(zhuǎn)換]瞬捕,在需要時自動插入(安全的)轉(zhuǎn)換:
fun demo(x: Any) {
if (x is String) {
print(x.length) // x 自動轉(zhuǎn)換為字符串
}
}
if (x !is String) return
print(x.length) // x 自動轉(zhuǎn)換為字符串
// `||` 右側(cè)的 x 自動轉(zhuǎn)換為字符串
if (x !is String || x.length == 0) return
// `&&` 右側(cè)的 x 自動轉(zhuǎn)換為字符串
if (x is String && x.length > 0) {
print(x.length) // x 自動轉(zhuǎn)換為字符串
}
//用在when表達(dá)式和while循環(huán)也是一樣
when (x) {
is Int -> print(x + 1)
is String -> print(x.length + 1)
is IntArray -> print(x.sum())
}
不安全轉(zhuǎn)換操作符
如果轉(zhuǎn)換是不可能的,轉(zhuǎn)換操作符會拋出一個異常舵抹,因此肪虎,它是不安全的。 Kotlin 中的不安全轉(zhuǎn)換由操作符 as完成惧蛹;可空類型和不可空類型是不同的類型扇救,不能轉(zhuǎn)換,比如:String
和String?
是不同的類型香嗓。
安全轉(zhuǎn)換操作符
為了避免拋出異常迅腔,可以使用安全轉(zhuǎn)換操作符 as? ,它可以在失敗時返回 null:
//as?右邊是非空類型的靠娱,聲明是可空類型的沧烈,若用as就會拋出異常,這里使用as?可以返回null像云,所以結(jié)果是可空的
val x: String? = y as? String
作用域函數(shù)
kotlin標(biāo)準(zhǔn)庫包含幾個函數(shù)锌雀,它們的唯一目的是在對象的上下文中執(zhí)行代碼塊。當(dāng)對一個對象調(diào)用這樣的函數(shù)并提供一個lambda表達(dá)式時迅诬,他會形成一個臨時作用域腋逆。在此作用域中,可以訪問該對象而無需其名稱侈贷,這些函數(shù)稱為作用域函數(shù)
這些函數(shù)基本上做了同樣的事情:在一個對象上執(zhí)行一個代碼塊惩歉。不同的是這個對象在塊中如何使用,以及整個表達(dá)式的結(jié)果是什么。
下面是作用域函數(shù)的典型用法:
Person("Alice", 20, "Amsterdam").let {
println(it)
it.moveTo("London")
it.incrementAge()
println(it)
}
如果不使用 let
來寫這段代碼柬泽,就必須引入一個新變量,并在每次使用它時重復(fù)其名稱嫁蛇。
val alice = Person("Alice", 20, "Amsterdam")
println(alice)
alice.moveTo("London")
alice.incrementAge()
println(alice)
作用域函數(shù)沒有引入任何新的技術(shù)锨并,但是它們可以使你的代碼更加簡潔易讀。
區(qū)別
標(biāo)準(zhǔn)庫中提供了幾個作用域函數(shù)睬棚,他們本質(zhì)上都非常相似第煮,每個作用域函數(shù)之間有兩個主要區(qū)別:
- 引用上下文對象的方式
- 返回值
上下文對象:this還是it
this
this關(guān)鍵字可看作為lambda 表達(dá)式的的接收著。run
抑党、with
以及 apply
通過關(guān)鍵字 this
引用上下文對象包警,在它們的 lambda 表達(dá)式中可以像在普通的類函數(shù)中一樣訪問上下文對象。在大多數(shù)場景底靠,當(dāng)你訪問接收者對象時你可以省略 this
害晦,來讓你的代碼更簡短。
val adam = Person("Adam").apply {
age = 20 // 和 this.age = 20 或者 adam.age = 20 一樣
city = "London"
}
println(adam)
it
it關(guān)鍵字可看作為作為 lambda 表達(dá)式的參數(shù)暑中。例如let
及 also
將上下文對象作為 lambda 表達(dá)式參數(shù)壹瘟,如果沒有指定參數(shù)名,對象可以用隱式默認(rèn)名稱 it
訪問
fun getRandomInt(): Int {
return Random.nextInt(100).also {
writeToLog("getRandomInt() generated value $it")
}
}
val i = getRandomInt()
此外鳄逾,當(dāng)將上下文對象作為參數(shù)傳遞時稻轨,可以為上下文對象指定在作用域內(nèi)的自定義名稱。
fun getRandomInt(): Int {
//自定義名稱value
return Random.nextInt(100).also { value ->
writeToLog("getRandomInt() generated value $value")
}
}
val i = getRandomInt()
返回值
根據(jù)返回結(jié)果雕凹,作用域函數(shù)可以分為以下兩類:
-
apply
及also
返回上下文對象殴俱。 -
let
、run
及with
返回 lambda 表達(dá)式結(jié)果.
返回上下文對象
apply
及 also
的返回值是上下文對象本身枚抵。
val numberList = mutableListOf<Double>()
//返回的是對象线欲,可以鏈?zhǔn)秸{(diào)用
numberList.also { println("Populating the list") }
.apply {
add(2.71)
add(3.14)
add(1.0)
}
.also { println("Sorting the list") }
.sort()
當(dāng)然,也還可以用在返回上下文對象的函數(shù)的 return 語句中
fun getRandomInt(): Int {
return Random.nextInt(100).also {
writeToLog("getRandomInt() generated value $it")
}
}
val i = getRandomInt()
返回表達(dá)式結(jié)果
let
汽摹、run
及 with
返回 lambda 表達(dá)式的結(jié)果
val numbers = mutableListOf("one", "two", "three")
val countEndsWithE = numbers.run {
add("four")
add("five")
count { it.endsWith("e") }//返回的結(jié)果
}
//There are 3 elements that end with e.
println("There are $countEndsWithE elements that end with e.")
此外询筏,還可以忽略返回值,僅使用作用域函數(shù)為變量創(chuàng)建一個臨時作用域竖慧。
val numbers = mutableListOf("one", "two", "three")
with(numbers) {
val firstItem = first()
val lastItem = last()
println("First item: $firstItem, last item: $lastItem")
}
函數(shù)選擇
前文提到了幾個函數(shù)嫌套,那么怎么選擇合適的作用域函數(shù)呢?圾旨,下面是列舉了它們之間的主要區(qū)別表
函數(shù) | 對象引用 | 返回值 | 是否是擴(kuò)展函數(shù) |
---|---|---|---|
let |
it |
Lambda 表達(dá)式結(jié)果 | 是 |
run |
this |
Lambda 表達(dá)式結(jié)果 | 是 |
run |
- | Lambda 表達(dá)式結(jié)果 | 不是:調(diào)用無需上下文對象 |
with |
this |
Lambda 表達(dá)式結(jié)果 | 不是:把上下文對象當(dāng)做參數(shù) |
apply |
this |
上下文對象 | 是 |
also |
it |
上下文對象 | 是 |
一下是對作用域函數(shù)的簡短總結(jié):
- 對一個非空(non-null)對象執(zhí)行 lambda 表達(dá)式:
let
- 將表達(dá)式作為變量引入為局部作用域中:
let
fun main() {
val nickName = "leavesC"
val also = nickName.let {
it.length
}
println(also) //7
}
- 對象配置:
apply
val adam = Person("Adam").apply {
age = 32
city = "London"
}
println(adam)
- 對象配置并且計算結(jié)果:
run
val service = MultiportService("https://example.kotlinlang.org", 80)
val result = service.run {
port = 8080
query(prepareRequest() + " to port $port")
}
- 在需要表達(dá)式的地方運行語句:非擴(kuò)展的
run
val hexNumberRegex = run {
val digits = "0-9"
val hexDigits = "A-Fa-f"
val sign = "+-"
Regex("[$sign]?[$digits$hexDigits]+")
}
for (match in hexNumberRegex.findAll("+1234 -FFFF not-a-number")) {
println(match.value)
}
- 附加效果:
also
val numbers = mutableListOf("one", "two", "three")
numbers
.also { println("The list elements before adding new one: $it") }
.add("four")
- 一個對象的一組函數(shù)調(diào)用:
with
val result = with(StringBuilder()) {
append("leavesC")
append("\n")
for (letter in 'A'..'Z') {
append(letter)
}
toString()
}
println(result)
takeIf
與 takeUnless
除了作用域函數(shù)外踱讨,標(biāo)準(zhǔn)庫還包含函數(shù) takeIf
及 takeUnless
。這倆函數(shù)使你可以將對象狀態(tài)檢查嵌入到調(diào)用鏈中砍的。
takeIf 接收一個返回值類型為 bool 的函數(shù)痹筛,當(dāng)該參數(shù)返回值為 true 時返回接受者對象本身,否則返回 null。
takeUnless 的判斷條件與 takeIf 相反帚稠。
fun main() {
println(check("leavesC")) //7
println(check(null)) //0
}
fun check(name: String?): Int {
return name.takeIf { !it.isNullOrBlank() }?.length ?: 0
}