Kotlin的其他技術
目錄
一、解構聲明
二秉沼、區(qū)間
三桶雀、類型檢查與轉換
四、this表達式
五唬复、相等性
六矗积、操作符重載
七、空安全
八敞咧、異常
九棘捣、類型別名
一、解構聲明
解構聲明能同時創(chuàng)建多個變量休建,將對象中的數(shù)據(jù)解析成相對的變量乍恐。舉個例子:
//創(chuàng)建一個數(shù)據(jù)類User
data class User(var name: String, var age: Int)
//獲得User的實例
var user = User("Czh", 22)
//聲明變量 name 和 age
var (name, age) = user
println("name:$name age:$age")
//輸出結果為:name:Czh age:22
上面代碼中用解構聲明同時創(chuàng)建兩個變量的時候,會被編譯成以下代碼:
//指定變量name的值為user第一個參數(shù)的值
var name = user.component1()
//指定變量name的值為user第二個參數(shù)的值
var age = user.component2()
println("name:$name age:$age")
//輸出結果為:name:Czh age:22
- 解構聲明和Map
Map可以保存一組key-value鍵值對测砂,通過解構聲明可以把這些值解構出來茵烈。如下所示:
var map = mutableMapOf<String, Any>()
map.put("name", "Czh")
map.put("age", 22)
for ((key, value) in map) {
println("$key:$value")
}
運行代碼,輸出結果:二砌些、區(qū)間
1.in
假如現(xiàn)在要判斷 i 是否在 1-5 內呜投,可以這樣寫:
if (i in 1..5) {
println("i 在 1-5 內")
}
上面代碼中,1..5
指的是 1-5存璃,in
指的是在...范圍內宙彪,如果 i 在范圍 1-5 之內,將會執(zhí)行后面的代碼塊有巧,輸出結果。如果想判斷 i 是否不在 1-5 內悲没,可以這樣寫:
//!in表示不在...范圍內
if (i !in 1..5) {
println("i 不在 1-5 內")
}
上面兩段代碼等同于:
if (i >= 1 && i <= 5) {
println("i 在 1-5 內")
}
if (i <= 1 && i >= 5) {
println("i 不在 1-5 內")
}
2.downTo
如果想輸出 1-5 篮迎,可以這樣寫:
for (i in 1..5) println(i)
//輸出12345
如果倒著來:
for (i in 5..1) println(i)
//什么也不輸出
這個時候可以用downTo
函數(shù)倒序輸出 5-1
for (i in 5 downTo 1) println(i)
3.step
上面的代碼順序輸出12345或倒序54321男图,按順序+1或者-1,也就是步長為1甜橱。如果要修改步長逊笆,可以用step
函數(shù),如下所示:
for (i in 1..5 step 2) println(i)
//輸出135
//倒序
for (i in 1 downTo 5 step 2) println(i)
//輸出531
4.until
上面的代碼中岂傲,使用的范圍都是閉區(qū)間难裆,例如1..5
的區(qū)間是[1,5],如果要創(chuàng)建一個不包括其結束元素的區(qū)間镊掖,即區(qū)間是[1,5)乃戈,可以使用until
函數(shù),如下所示:
for (i in 1 until 5) println(i)
//輸出1234
三亩进、類型檢查與轉換
1.is操作符
在Kotlin中症虑,可以通過is
操作符判斷一個對象與指定的類型是否一致,還可以使用is
操作符的否定形式!is
归薛,舉個例子:
var a: Any = "a"
if (a is String) {
println("a是String類型")
}
if (a !is Int) {
println("a不是Int類型")
}
運行代碼谍憔,輸出結果為:2.智能轉換
在Kotlin中不必使用顯式類型轉換操作,因為編譯器會跟蹤不可變值的is
檢查以及顯式轉換主籍,并在需要時自動插入(安全的)轉換习贫。舉個例子:
var a: Any = "a"
if (a is String) {
println("a是String類型")
println(a.length) // a 自動轉換為String類型
//輸出結果為:1
}
還可以反向檢查,如下所示:
if (a !is String) return
print(a.length) // a 自動轉換為String類型
在 && 和 || 的右側也可以智能轉換:
// `&&` 右側的 a 自動轉換為String
if (a is String && a.length > 0)
// `||` 右側的 a 自動轉換為String
if (a is String || a.length > 0)
在when表達式和while循環(huán)里也能智能轉換:
when(a){
is String -> a.length
is Int -> a + 1
}
需要注意的是千元,當編譯器不能保證變量在檢查和使用之間不可改變時苫昌,智能轉換不能用。智能轉換能否適用根據(jù)以下規(guī)則:
- val 局部變量——總是可以诅炉,局部委托屬性除外呵曹;
- val 屬性——如果屬性是 private 或 internal妙蔗,或者該檢查在聲明屬性的同一模塊中執(zhí)行。智能轉換不適用于 open 的屬性或者具有自定義 getter 的屬性;
- var 局部變量——如果變量在檢查和使用之間沒有修改冲簿、沒有在會修改它的 lambda 中捕獲、并且不是局部委托屬性晚缩;
- var 屬性——決不可能(因為該變量可以隨時被其他代碼修改)
3.強制類型轉換
在Kotlin中浅役,用操作符as
進行強制類型轉換,如下所示:
var any: Any = "abc"
var str: String = any as String
但強制類型轉換是不安全的瞻凤,如果類型不兼容憨攒,會拋出一個異常,如下所示:
var int: Int = 123
var str: String = int as String
//拋出ClassCastException
4.可空轉換操作符
null
不能轉換為 String
阀参,因該類型不是可空的肝集。舉個例子:
var str = null
var str2 = str as String
//拋出TypeCastException
解決這個問題可以使用可空轉換操作符as?
,如下所示:
var str = null
var str2 = str as? String
println(str2) //輸出結果為:null
使用安全轉換操作符as?
可以在轉換失敗時返回null
蛛壳,避免了拋出異常杏瞻。
四所刀、this表達式
為了表示當前的接收者我們使用this表達式。當this
在類的成員中捞挥,this
指的是該類的當前對象;當this
在擴展函數(shù)或者帶接收者的函數(shù)字面值中浮创,this
表示在點左側傳遞的接收者參數(shù)。
- 限定的this
如果this
沒有限定符砌函,它指的是最內層的包含它的作用域斩披。如果要訪問來自外部作用域的this
(一個類或者擴展函數(shù), 或者帶標簽的帶接收者的函數(shù)字面值)我們使用this@label
讹俊,其中@label
是一個代指this
來源的標簽垦沉。舉個例子:
class A { // 隱式標簽 @A
inner class B { // 隱式標簽 @B
fun Int.foo() { // 隱式標簽 @foo
val a = this@A // A 的 this
val b = this@B // B 的 this
val c = this // foo() 的接收者,一個 Int
val c1 = this@foo // foo() 的接收者劣像,一個 Int
val funLit = lambda@ fun String.() {
val d = this // funLit 的接收者
}
val funLit2 = { s: String ->
// foo() 的接收者乡话,因為它包含的 lambda 表達式
// 沒有任何接收者
val d1 = this
}
}
}
}
五、相等性
在Kotlin中存在結構相等和引用相等兩中相等判斷耳奕。
1.結構相等
使用equals()
或==
判斷绑青,如下所示:
var a = "1"
var b = "1"
if (a.equals(b)) {
println("a 和 b 結構相等")
//輸出結果為:a 和 b 結構相等
}
var a = 1
var b = 1
if (a == b) {
println("a 和 b 結構相等")
//輸出結果為:a 和 b 結構相等
}
2.引用相等
引用相等指兩個引用指向同一對象,用===
判斷屋群,如下所示:
data class User(var name: String, var age: Int)
var a = User("Czh", 22)
var b = User("Czh", 22)
var c = b
var d = a
if (c == d) {
println("a 和 b 結構相等")
} else {
println("a 和 b 結構不相等")
}
if (c === d) {
println("a 和 b 引用相等")
} else {
println("a 和 b 引用不相等")
}
運行代碼闸婴,輸出結果為:六、操作符重載
Kotlin允許對自己的類型提供預定義的一組操作符的實現(xiàn)芍躏,這些操作符具有固定的符號表示 (如 +
或 *
)和固定的優(yōu)先級邪乍。為實現(xiàn)這樣的操作符,我們?yōu)橄鄳念愋停炊僮鞣髠鹊念愋秃鸵辉僮鞣膮?shù)類型)提供了一個固定名字的成員函數(shù)或擴展函數(shù)对竣。 重載操作符的函數(shù)需要用 operator
修飾符標記庇楞。
重載操作符
+
是一個一元操作符,下面來對一元操作符進行重載:
//用 operator 修飾符標記
operator fun String.unaryPlus(): String {
return this + this
}
//調用
var a = "a"
println(+a) //輸出結果為:aa
當編譯器處理例如表達式 +a 時否纬,它執(zhí)行以下步驟:
- 確定 a 的類型吕晌,令其為 T;
- 為接收者 T 查找一個帶有 operator 修飾符的無參函數(shù) unaryPlus()临燃,即成員函數(shù)或擴展函數(shù)睛驳;
- 如果函數(shù)不存在或不明確,則導致編譯錯誤膜廊;
- 如果函數(shù)存在且其返回類型為 R乏沸,那就表達式 +a 具有類型 R;
除對一元操作符進行重載外爪瓜,還可以對其他操作符進行重載蹬跃,其重載方式和原理大致相同。下面來一一列舉:
1.一元操作符
表達式 | 對應的函數(shù) |
---|---|
+a | a.unaryPlus() |
-a | a.unaryMinus() |
!a | a.not() |
a++ | a.inc() |
a-- | a.dec() |
2.二元操作符
表達式 | 對應的函數(shù) |
---|---|
a+b | a.plus(b) |
a-b | a.minus(b) |
a*b | a.times(b) |
a/b | a.div(b) |
a%b | a.mod(b) |
a..b | a.rangeTo(b) |
3.in操作符
表達式 | 對應的函數(shù) |
---|---|
a in b | b.contains(a) |
a !in b | !b.contains(a) |
4.索引訪問操作符
表達式 | 對應的函數(shù) |
---|---|
a[i] | a.get(i) |
a[i, j] | a.get(i, j) |
a[i_1, ……, i_n] | a.get(i_1, ……, i_n) |
a[i] = b | a.set(i, b) |
a[i, j] = b | a.set(i, j, b) |
a[i_1, ……, i_n] = b | a.set(i_1, ……, i_n, b) |
5.調用操作符
表達式 | 對應的函數(shù) |
---|---|
a() | a.invoke() |
a(i) | a.invoke(i) |
a(i, j) | a.invoke(i, j) |
a(i_1, ……, i_n) | a.invoke(i_1, ……, i_n) |
6.廣義賦值
表達式 | 對應的函數(shù) |
---|---|
a += b | a.plusAssign(b) |
a -= b | a.minusAssign(b) |
a *= b | a.timesAssign(b) |
a /= b | a.divAssign(b) |
a %= b | a.remAssign(b), a.modAssign(b)(已棄用) |
7.相等與不等操作符
表達式 | 對應的函數(shù) |
---|---|
a == b | a?.equals(b) ?: (b === null) |
a != b | !(a?.equals(b) ?: (b === null)) |
8.比較操作符
表達式 | 對應的函數(shù) |
---|---|
a > b | a.compareTo(b) > 0 |
a < b | a.compareTo(b) < 0 |
a >= b | a.compareTo(b) >= 0 |
a <= b | a.compareTo(b) <= 0 |
七铆铆、空安全
在Java中蝶缀,NullPointerException 可能是最常見的異常之一辆苔,而Kotlin的類型系統(tǒng)旨在消除來自代碼空引用的危險。
1.可空類型與非空類型
在Kotlin中扼劈,只有下列情況可能導致出現(xiàn)NullPointerException:
- 顯式調用 throw NullPointerException();
- 使用了下文描述的 !! 操作符菲驴;
- 有些數(shù)據(jù)在初始化時不一致荐吵;
- 外部 Java 代碼引發(fā)的問題。
在 Kotlin 中赊瞬,類型系統(tǒng)區(qū)分一個引用可以容納 null (可空引用)還是不能容納(非空引用)先煎。 例如,String 類型的常規(guī)變量不能容納 null:
如果要允許為空巧涧,我們可以聲明一個變量為可空字符串薯蝎,在字符串類型后面加一個問號
?
,寫作 String?
谤绳,如下所示:
var b: String? = "b"
b = null
2.安全調用操作符
接著上面的代碼占锯,如果你調用a
的方法或者訪問它的屬性,不會出現(xiàn)NullPointerException缩筛,但如果調用b
的方法或者訪問它的屬性消略,編譯器會報告一個錯誤,如下所示:
這個時候可以使用安全調用操作符瞎抛,寫作
?.
艺演,在b
后面加安全調用操作符,表示如果b
不為null則調用b.length
桐臊,如下所示:
b?.length
安全調用操作符還能鏈式調用胎撤,例如一個員工 Bob 可能會(或者不會)分配給一個部門, 并且可能有另外一個員工是該部門的負責人断凶,那么獲取 Bob 所在部門負責人(如果有的話)的名字伤提,我們寫作:
Bob?.department?.head?.name
//如果Bob分配給一個部門
//執(zhí)行Bob.department.head?獲取該部門的負責人
//如果該部門有一個負責人
//執(zhí)行Bob.department.head.name獲取該負責人的名字
如果該鏈式調用中任何一個屬性為null,整個表達式都會返回null懒浮。
如果要只對非空值執(zhí)行某個操作飘弧,安全調用操作符可以與let
一起使用,如下所示:
val listWithNulls: List<String?> = listOf("A", null, "B")
for (item in listWithNulls) {
item?.let { println(it) }
}
運行代碼砚著,輸出結果為:- 安全的類型轉換
如果對象不是目標類型次伶,那么常規(guī)類型轉換可能會導致ClassCastException
。 另一個選擇是使用安全的類型轉換稽穆,如果嘗試轉換不成功則返回null冠王,如下所示:
val i: Int? = i as? Int
- 可空類型的集合
如果你有一個可空類型元素的集合,并且想要過濾非空元素舌镶,你可以使用filterNotNull
來實現(xiàn)柱彻。如下所示:
val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()
3.Elvis 操作符
先看一段代碼:
val i: Int = if (b != null) b.length else -1
val i = b?.length ?: -1
這兩行代碼表達的都是“如果b
不等于null豪娜,i = b.length
;如果b
等于null,i = -1
”。第一行代碼用的是if
表達式哟楷,而第二行代碼使用了Elvis
操作符瘤载,寫作?:
。Elvis
操作符表示如果?:
左側表達式非空卖擅,就使用左側表達式鸣奔,否則使用右側表達式。
請注意惩阶,因為throw
和return
在Kotlin中都是表達式挎狸,所以它們也可以用在Elvis
操作符右側。如下所示:
fun foo(node: Node): String? {
val parent = node.getParent() ?: return null
val name = node.getName() ?: throw IllegalArgumentException("name expected")
// ……
}
4. !! 操作符
!!
操作符將任何值轉換為非空類型断楷,若該值為空則拋出異常锨匆。如下所示:
var a = null
a!!
//運行代碼,拋出KotlinNullPointerException
八冬筒、異常
Kotlin中所有異常類都是Throwable
類的子類恐锣。每個異常都有消息、堆椪饲В回溯信息和可選的原因侥蒙。
使用throw
表達式可以拋出異常。舉個例子:
throw NullPointerException("NPE")
使用try
表達式可以捕獲異常匀奏。一個try
表達式可以有多個catch
代碼段鞭衩;finally
代碼段可以省略。舉個例子:
try {
//捕獲異常
} catch (e: NullPointerException) {
//異常處理
} catch (e: ClassNotFoundException) {
//異常處理
} finally {
//可選的finally代碼段
}
因為Try
是一個表達式娃善,所以它可以有一個返回值论衍。舉個例子:
val a: Int? = try {
parseInt(input)
} catch (e: NumberFormatException) {
null
}
try
表達式的返回值是 try
塊中的最后一個表達式或者是catch
塊中的最后一個表達式。finally
塊中的內容不會影響表達式的結果聚磺。
九坯台、類型別名
Kotlin提供類型別名來代替過長的類型名稱,這些類型別名不會引入新類型瘫寝,且等效于相應的底層類型蜒蕾。可以通過使用關鍵字typealias
修改類型別名焕阿,如下所示:
//使用關鍵字typealias修改類型別名Length
//相當于 Length 就是一個 (String) -> Int 類型
typealias Length = (String) -> Int
//調用
fun getLength(l: Length) = l("Czh")
//編譯器把 Length 擴展為 (String) -> Int 類型
val l: Length = { it.length }
println(getLength(l)) //輸出結果為:3
使用類型別名能讓那些看起來很長的類型在使用起來變得簡潔咪啡,如下所示:
typealias MyType = (String, Int, Any, MutableList<String> ) -> Unit
//當我們使用的時候
var myType:MyType
//而不需要寫他原來的類型
//var myType:(String, Int, Any, MutableList<String> ) -> Unit
總結
相對于Java來說,Kotlin有很多新的技術和語法糖暮屡,這也是為什么使用Kotlin來開發(fā)Android要優(yōu)于Java撤摸。運用好這些新的東西,能大大加快開發(fā)速度。
參考文獻:
Kotlin語言中文站准夷、《Kotlin程序開發(fā)入門精要》
推薦閱讀:
從Java到Kotlin(一)為什么使用Kotlin
從Java到Kotlin(二)基本語法
從Java到Kotlin(三)類和接口
從Java到Kotlin(四)對象與泛型
從Java到Kotlin(五)函數(shù)與Lambda表達式
從Java到Kotlin(六)擴展與委托
從Java到Kotlin(七)反射和注解
Kotlin學習資料匯總
更多精彩文章請掃描下方二維碼關注微信公眾號"AndroidCzh":這里將長期為您分享原創(chuàng)文章钥飞、Android開發(fā)經(jīng)驗等!
QQ交流群: 705929135