從Java到Kotlin(八)

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操作符表示如果?:左側表達式非空卖擅,就使用左側表達式鸣奔,否則使用右側表達式。
請注意惩阶,因為throwreturn在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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末衫嵌,一起剝皮案震驚了整個濱河市读宙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌楔绞,老刑警劉巖论悴,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異墓律,居然都是意外死亡,警方通過查閱死者的電腦和手機幔亥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門耻讽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人帕棉,你說我怎么就攤上這事针肥。” “怎么了香伴?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵慰枕,是天一觀的道長。 經(jīng)常有香客問我即纲,道長具帮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任低斋,我火速辦了婚禮蜂厅,結果婚禮上,老公的妹妹穿的比我還像新娘膊畴。我一直安慰自己掘猿,他們只是感情好,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布唇跨。 她就那樣靜靜地躺著稠通,像睡著了一般。 火紅的嫁衣襯著肌膚如雪买猖。 梳的紋絲不亂的頭發(fā)上改橘,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天,我揣著相機與錄音政勃,去河邊找鬼唧龄。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的既棺。 我是一名探鬼主播讽挟,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼丸冕!你這毒婦竟也來了耽梅?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤胖烛,失蹤者是張志新(化名)和其女友劉穎眼姐,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體佩番,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡众旗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了趟畏。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贡歧。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖赋秀,靈堂內的尸體忽然破棺而出利朵,到底是詐尸還是另有隱情,我是刑警寧澤猎莲,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布绍弟,位于F島的核電站,受9級特大地震影響著洼,放射性物質發(fā)生泄漏樟遣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一身笤、第九天 我趴在偏房一處隱蔽的房頂上張望年碘。 院中可真熱鬧,春花似錦展鸡、人聲如沸屿衅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涤久。三九已至,卻和暖如春忍弛,著一層夾襖步出監(jiān)牢的瞬間响迂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工细疚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蔗彤,地道東北人。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像然遏,于是被迫代替她去往敵國和親贫途。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內容