学习Kotlin(八)其他技术
?
?
推薦閱讀:
學(xué)習(xí)Kotlin(一)為什么使用Kotlin
學(xué)習(xí)Kotlin(二)基本語法
學(xué)習(xí)Kotlin(三)類和接口
學(xué)習(xí)Kotlin(四)對(duì)象與泛型
學(xué)習(xí)Kotlin(五)函數(shù)與Lambda表達(dá)式
學(xué)習(xí)Kotlin(六)擴(kuò)展與委托
學(xué)習(xí)Kotlin(七)反射和注解
學(xué)習(xí)Kotlin(八)其他技術(shù)
Kotlin學(xué)習(xí)資料總匯
?
目錄
一、解構(gòu)聲明
二、區(qū)間
三、類型檢查與轉(zhuǎn)換
四、this表達(dá)式
五、相等性
六、操作符重載
七、空安全
八、異常
九、類型別名
一、解構(gòu)聲明
解構(gòu)聲明能同時(shí)創(chuàng)建多個(gè)變量,將對(duì)象中的數(shù)據(jù)解析成相對(duì)的變量。舉個(gè)例子:
//創(chuàng)建一個(gè)數(shù)據(jù)類User data class User(var name: String, var age: Int)//獲得User的實(shí)例 var user = User("Czh", 22) //聲明變量 name 和 age var (name, age) = userprintln("name:$name age:$age") //輸出結(jié)果為:name:Czh age:22上面代碼中用解構(gòu)聲明同時(shí)創(chuàng)建兩個(gè)變量的時(shí)候,會(huì)被編譯成以下代碼:
//指定變量name的值為user第一個(gè)參數(shù)的值 var name = user.component1() //指定變量name的值為user第二個(gè)參數(shù)的值 var age = user.component2()println("name:$name age:$age") //輸出結(jié)果為:name:Czh age:22- 解構(gòu)聲明和Map
Map可以保存一組key-value鍵值對(duì),通過解構(gòu)聲明可以把這些值解構(gòu)出來。如下所示:
運(yùn)行代碼,輸出結(jié)果:
二、區(qū)間
1.in
假如現(xiàn)在要判斷 i 是否在 1-5 內(nèi),可以這樣寫:
if (i in 1..5) {println("i 在 1-5 內(nèi)") }上面代碼中,1..5指的是 1-5,in指的是在...范圍內(nèi),如果 i 在范圍 1-5 之內(nèi),將會(huì)執(zhí)行后面的代碼塊,輸出結(jié)果。如果想判斷 i 是否不在 1-5 內(nèi),可以這樣寫:
//!in表示不在...范圍內(nèi) if (i !in 1..5) {println("i 不在 1-5 內(nèi)") }上面兩段代碼等同于:
if (i >= 1 && i <= 5) {println("i 在 1-5 內(nèi)") } if (i <= 1 && i >= 5) {println("i 不在 1-5 內(nèi)") }2.downTo
如果想輸出 1-5 ,可以這樣寫:
for (i in 1..5) println(i) //輸出12345如果倒著來:
for (i in 5..1) println(i) //什么也不輸出這個(gè)時(shí)候可以用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) //輸出5314.until
上面的代碼中,使用的范圍都是閉區(qū)間,例如1..5的區(qū)間是[1,5],如果要?jiǎng)?chuàng)建一個(gè)不包括其結(jié)束元素的區(qū)間,即區(qū)間是[1,5),可以使用until函數(shù),如下所示:
for (i in 1 until 5) println(i) //輸出1234三、類型檢查與轉(zhuǎn)換
1.is操作符
在Kotlin中,可以通過is操作符判斷一個(gè)對(duì)象與指定的類型是否一致,還可以使用is操作符的否定形式!is,舉個(gè)例子:
var a: Any = "a" if (a is String) {println("a是String類型") } if (a !is Int) {println("a不是Int類型") }運(yùn)行代碼,輸出結(jié)果為:
2.智能轉(zhuǎn)換
在Kotlin中不必使用顯式類型轉(zhuǎn)換操作,因?yàn)榫幾g器會(huì)跟蹤不可變值的is檢查以及顯式轉(zhuǎn)換,并在需要時(shí)自動(dòng)插入(安全的)轉(zhuǎn)換。舉個(gè)例子:
var a: Any = "a" if (a is String) {println("a是String類型")println(a.length) // a 自動(dòng)轉(zhuǎn)換為String類型//輸出結(jié)果為:1 }還可以反向檢查,如下所示:
if (a !is String) return print(a.length) // a 自動(dòng)轉(zhuǎn)換為String類型在 && 和 || 的右側(cè)也可以智能轉(zhuǎn)換:
// `&&` 右側(cè)的 a 自動(dòng)轉(zhuǎn)換為String if (a is String && a.length > 0)// `||` 右側(cè)的 a 自動(dòng)轉(zhuǎn)換為String if (a !is String || a.length > 0)在when表達(dá)式和while循環(huán)里也能智能轉(zhuǎn)換:
when(a){is String -> a.lengthis Int -> a + 1 }需要注意的是,當(dāng)編譯器不能保證變量在檢查和使用之間不可改變時(shí),智能轉(zhuǎn)換不能用。智能轉(zhuǎn)換能否適用根據(jù)以下規(guī)則:
- val?局部變量——總是可以,局部委托屬性除外;
- val?屬性——如果屬性是 private 或 internal,或者該檢查在聲明屬性的同一模塊中執(zhí)行。智能轉(zhuǎn)換不適用于 open 的屬性或者具有自定義 getter 的屬性;
- var?局部變量——如果變量在檢查和使用之間沒有修改、沒有在會(huì)修改它的 lambda 中捕獲、并且不是局部委托屬性;
- var?屬性——決不可能(因?yàn)樵撟兞靠梢噪S時(shí)被其他代碼修改)
3.強(qiáng)制類型轉(zhuǎn)換
在Kotlin中,用操作符as進(jìn)行強(qiáng)制類型轉(zhuǎn)換,如下所示:
var any: Any = "abc" var str: String = any as String但強(qiáng)制類型轉(zhuǎn)換是不安全的,如果類型不兼容,會(huì)拋出一個(gè)異常,如下所示:
var int: Int = 123 var str: String = int as String //拋出ClassCastException4.可空轉(zhuǎn)換操作符
null不能轉(zhuǎn)換為?String,因該類型不是可空的。舉個(gè)例子:
var str = null var str2 = str as String //拋出TypeCastException解決這個(gè)問題可以使用可空轉(zhuǎn)換操作符as?,如下所示:
var str = null var str2 = str as? String println(str2) //輸出結(jié)果為:null使用安全轉(zhuǎn)換操作符as?可以在轉(zhuǎn)換失敗時(shí)返回null,避免了拋出異常。
四、this表達(dá)式
為了表示當(dāng)前的接收者我們使用this表達(dá)式。當(dāng)this在類的成員中,this指的是該類的當(dāng)前對(duì)象;當(dāng)this在擴(kuò)展函數(shù)或者帶接收者的函數(shù)字面值中,this表示在點(diǎn)左側(cè)傳遞的接收者參數(shù)。
- 限定的this如果this沒有限定符,它指的是最內(nèi)層的包含它的作用域。如果要訪問來自外部作用域的this(一個(gè)類或者擴(kuò)展函數(shù), 或者帶標(biāo)簽的帶接收者的函數(shù)字面值)我們使用this@label,其中?@label?是一個(gè)代指this來源的標(biāo)簽。舉個(gè)例子:
五、相等性
在Kotlin中存在結(jié)構(gòu)相等和引用相等兩中相等判斷。
1.結(jié)構(gòu)相等
使用equals()或==判斷,如下所示:
var a = "1" var b = "1" if (a.equals(b)) {println("a 和 b 結(jié)構(gòu)相等")//輸出結(jié)果為:a 和 b 結(jié)構(gòu)相等 }var a = 1 var b = 1 if (a == b) {println("a 和 b 結(jié)構(gòu)相等")//輸出結(jié)果為:a 和 b 結(jié)構(gòu)相等 }2.引用相等
引用相等指兩個(gè)引用指向同一對(duì)象,用===判斷,如下所示:
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 結(jié)構(gòu)相等") } else {println("a 和 b 結(jié)構(gòu)不相等") } if (c === d) {println("a 和 b 引用相等") } else {println("a 和 b 引用不相等") }運(yùn)行代碼,輸出結(jié)果為:
六、操作符重載
Kotlin允許對(duì)自己的類型提供預(yù)定義的一組操作符的實(shí)現(xiàn),這些操作符具有固定的符號(hào)表示 (如?+?或?*)和固定的優(yōu)先級(jí)。為實(shí)現(xiàn)這樣的操作符,我們?yōu)橄鄳?yīng)的類型(即二元操作符左側(cè)的類型和一元操作符的參數(shù)類型)提供了一個(gè)固定名字的成員函數(shù)或擴(kuò)展函數(shù)。 重載操作符的函數(shù)需要用?operator?修飾符標(biāo)記。
重載操作符
+是一個(gè)一元操作符,下面來對(duì)一元操作符進(jìn)行重載:
//用 operator 修飾符標(biāo)記 operator fun String.unaryPlus(): String {return this + this }//調(diào)用 var a = "a" println(+a) //輸出結(jié)果為:aa當(dāng)編譯器處理例如表達(dá)式 +a 時(shí),它執(zhí)行以下步驟:
- 確定 a 的類型,令其為 T;
- 為接收者 T 查找一個(gè)帶有 operator 修飾符的無參函數(shù) unaryPlus(),即成員函數(shù)或擴(kuò)展函數(shù);
- 如果函數(shù)不存在或不明確,則導(dǎo)致編譯錯(cuò)誤;
- 如果函數(shù)存在且其返回類型為 R,那就表達(dá)式 +a 具有類型 R;
除對(duì)一元操作符進(jìn)行重載外,還可以對(duì)其他操作符進(jìn)行重載,其重載方式和原理大致相同。下面來一一列舉:
1.一元操作符
| +a | a.unaryPlus() |
| -a | a.unaryMinus() |
| !a | a.not() |
| a++ | a.inc() |
| a-- | a.dec() |
2.二元操作符
| 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操作符
| a in b | b.contains(a) |
| a !in b | !b.contains(a) |
4.索引訪問操作符
| 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.調(diào)用操作符
| 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.廣義賦值
| 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.相等與不等操作符
| a == b | a?.equals(b) ?: (b === null) |
| a != b | !(a?.equals(b) ?: (b === null)) |
8.比較操作符
| 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)旨在消除來自代碼空引用的危險(xiǎn)。
1.可空類型與非空類型
在Kotlin中,只有下列情況可能導(dǎo)致出現(xiàn)NullPointerException:
- 顯式調(diào)用 throw NullPointerException();
- 使用了下文描述的 !! 操作符;
- 有些數(shù)據(jù)在初始化時(shí)不一致;
- 外部 Java 代碼引發(fā)的問題。
在 Kotlin 中,類型系統(tǒng)區(qū)分一個(gè)引用可以容納 null (可空引用)還是不能容納(非空引用)。 例如,String 類型的常規(guī)變量不能容納 null:
如果要允許為空,我們可以聲明一個(gè)變量為可空字符串,在字符串類型后面加一個(gè)問號(hào)?,寫作 String?,如下所示:
?
var b: String? = "b" b = null2.安全調(diào)用操作符
接著上面的代碼,如果你調(diào)用a的方法或者訪問它的屬性,不會(huì)出現(xiàn)NullPointerException,但如果調(diào)用b的方法或者訪問它的屬性,編譯器會(huì)報(bào)告一個(gè)錯(cuò)誤,如下所示:
這個(gè)時(shí)候可以使用安全調(diào)用操作符,寫作?.,在b后面加安全調(diào)用操作符,表示如果b不為null則調(diào)用b.length,如下所示:
?
b?.length安全調(diào)用操作符還能鏈?zhǔn)秸{(diào)用,例如一個(gè)員工 Bob 可能會(huì)(或者不會(huì))分配給一個(gè)部門, 并且可能有另外一個(gè)員工是該部門的負(fù)責(zé)人,那么獲取 Bob 所在部門負(fù)責(zé)人(如果有的話)的名字,我們寫作:
Bob?.department?.head?.name //如果Bob分配給一個(gè)部門 //執(zhí)行Bob.department.head?獲取該部門的負(fù)責(zé)人 //如果該部門有一個(gè)負(fù)責(zé)人 //執(zhí)行Bob.department.head.name獲取該負(fù)責(zé)人的名字如果該鏈?zhǔn)秸{(diào)用中任何一個(gè)屬性為null,整個(gè)表達(dá)式都會(huì)返回null。如果要只對(duì)非空值執(zhí)行某個(gè)操作,安全調(diào)用操作符可以與let一起使用,如下所示:
val listWithNulls: List<String?> = listOf("A", null, "B") for (item in listWithNulls) {item?.let { println(it) } }運(yùn)行代碼,輸出結(jié)果為:
- 安全的類型轉(zhuǎn)換
如果對(duì)象不是目標(biāo)類型,那么常規(guī)類型轉(zhuǎn)換可能會(huì)導(dǎo)致?ClassCastException。 另一個(gè)選擇是使用安全的類型轉(zhuǎn)換,如果嘗試轉(zhuǎn)換不成功則返回null,如下所示:
- 可空類型的集合
如果你有一個(gè)可空類型元素的集合,并且想要過濾非空元素,你可以使用filterNotNull來實(shí)現(xiàn)。如下所示:
3.Elvis 操作符
先看一段代碼:
val i: Int = if (b != null) b.length else -1 val i = b?.length ?: -1這兩行代碼表達(dá)的都是“如果b不等于null,i = b.length;如果b等于null,i = -1”。第一行代碼用的是if表達(dá)式,而第二行代碼使用了Elvis操作符,寫作?:。Elvis操作符表示如果?:左側(cè)表達(dá)式非空,就使用左側(cè)表達(dá)式,否則使用右側(cè)表達(dá)式。請(qǐng)注意,因?yàn)閠hrow和return在Kotlin中都是表達(dá)式,所以它們也可以用在Elvis操作符右側(cè)。如下所示:
fun foo(node: Node): String? {val parent = node.getParent() ?: return nullval name = node.getName() ?: throw IllegalArgumentException("name expected")// …… }4. !! 操作符
!!操作符將任何值轉(zhuǎn)換為非空類型,若該值為空則拋出異常。如下所示:
var a = null a!! //運(yùn)行代碼,拋出KotlinNullPointerException八、異常
Kotlin中所有異常類都是Throwable類的子類。每個(gè)異常都有消息、堆棧回溯信息和可選的原因。使用throw表達(dá)式可以拋出異常。舉個(gè)例子:
throw NullPointerException("NPE")使用try表達(dá)式可以捕獲異常。一個(gè)try表達(dá)式可以有多個(gè)catch代碼段;finally代碼段可以省略。舉個(gè)例子:
try {//捕獲異常 } catch (e: NullPointerException) {//異常處理 } catch (e: ClassNotFoundException) {//異常處理 } finally {//可選的finally代碼段 }因?yàn)門ry是一個(gè)表達(dá)式,所以它可以有一個(gè)返回值。舉個(gè)例子:
val a: Int? = try {parseInt(input) } catch (e: NumberFormatException) {null }try表達(dá)式的返回值是 try塊中的最后一個(gè)表達(dá)式或者是catch塊中的最后一個(gè)表達(dá)式。finally塊中的內(nèi)容不會(huì)影響表達(dá)式的結(jié)果。
九、類型別名
Kotlin提供類型別名來代替過長的類型名稱,這些類型別名不會(huì)引入新類型,且等效于相應(yīng)的底層類型。可以通過使用關(guān)鍵字typealias修改類型別名,如下所示:
//使用關(guān)鍵字typealias修改類型別名Length //相當(dāng)于 Length 就是一個(gè) (String) -> Int 類型 typealias Length = (String) -> Int//調(diào)用 fun getLength(l: Length) = l("Czh") //編譯器把 Length 擴(kuò)展為 (String) -> Int 類型 val l: Length = { it.length } println(getLength(l)) //輸出結(jié)果為:3使用類型別名能讓那些看起來很長的類型在使用起來變得簡潔,如下所示:
typealias MyType = (String, Int, Any, MutableList<String> ) -> Unit //當(dāng)我們使用的時(shí)候 var myType:MyType //而不需要寫他原來的類型 //var myType:(String, Int, Any, MutableList<String> ) -> Unit總結(jié)
相對(duì)于Java來說,Kotlin有很多新的技術(shù)和語法糖,這也是為什么使用Kotlin來開發(fā)Android要優(yōu)于Java。運(yùn)用好這些新的東西,能大大加快開發(fā)速度。
原文鏈接:https://juejin.im/post/5a9cdc7df265da2393768e06
總結(jié)
以上是生活随笔為你收集整理的学习Kotlin(八)其他技术的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 学习Kotlin(七)反射和注解
- 下一篇: Kotlin的基本数值类型问题:是对象?