Kotlin 時(shí)間線
- 2011年7月拜隧,JetBrains 推出 Kotlin 項(xiàng)目
- 2012年2月呻此,JetBrains 以 Apache 2.0 許可證開源此項(xiàng)目轮纫,github 地址 kotlin
- 2016年2月,Kotlin v1.0(第一個(gè)官方穩(wěn)定版本)發(fā)布
- 2017 Google I/O 大會上焚鲜,Kotlin 正式成為 Android 開發(fā)官方語言
什么是Kotlin
Kotlin 是由 JetBrains 開發(fā)團(tuán)隊(duì)設(shè)計(jì)的基于 JVM 的靜態(tài)型別編程語言
JetBrains開發(fā)團(tuán)隊(duì): 眾所周知掌唾,他們開發(fā)的 Intellij IDEA 是開發(fā) Java 最優(yōu)秀的 ide》薨酰可能是 Java 的局限性迫使他們吸收各個(gè)語言的優(yōu)點(diǎn)來創(chuàng)造出這門語言糯彬。IDEA(包括Android Studio)對 Kotlin 的支持是非常全面完善的,甚至對 Eclipse 也開發(fā)了插件兼容
基于JVM: Kotlin 同 Java 一樣贝乎,是編譯成字節(jié)碼情连,基于 Java Virtual Machine(Java 虛擬機(jī))運(yùn)行的語言。Kotlin 能做到和 Java 混合編譯览效,這也意味著 Java 豐富的第三方庫能被 Kotlin 無條件繼承
靜態(tài)語言: 靜態(tài)類型語言是在運(yùn)行前編譯時(shí)檢查類型却舀,而不是在運(yùn)行期間檢查數(shù)據(jù)的類型。Kotlin 必須清楚定義數(shù)據(jù)類型锤灿,否則無法編譯通過挽拔,同 Java 一樣,Kotlin 是強(qiáng)靜態(tài)語言但校,類型轉(zhuǎn)換必須顯式表達(dá)
為什么我們要使用 Kotlin
我們可以在這個(gè)網(wǎng)站 https://fabiomsr.github.io/from-java-to-kotlin 上看到 Kotlin 和 Java 的基本語法比較螃诅,很明顯 Kotlin 擁有比 Java 更簡潔的語法
相比較 Java,Kotlin 增加了許多特性术裸,比如 When 表達(dá)式倘是、擴(kuò)展函數(shù)、高級類袭艺、委托搀崭、Java 8 之前沒有的 Lambda 表達(dá)式、高階函數(shù)猾编、協(xié)程... 每一個(gè)都讓人興奮不已
相比較 Java瘤睹,Kotlin 去除了一些特性,比如 final 關(guān)鍵字答倡、靜態(tài)變量轰传、靜態(tài)方法等
相比較 Java,Kotlin 封裝了許多過程和表示方法瘪撇,實(shí)現(xiàn)許多語法糖获茬,比如解構(gòu)聲明、操作符重載倔既、空安全... 可以大幅減少代碼锦茁,提高效率
列舉一下 Kotlin 純語法上的的好處
開源:開源的好處其實(shí)也不用多說,其帶來的龐大的社區(qū)及活躍的用戶會使這門語言充滿了活力叉存,會涌現(xiàn)出一大片優(yōu)秀的項(xiàng)目和開源庫
Java 的兼容性:Kotlin 的設(shè)計(jì)之初就考慮到了對 Java 代碼的兼容性,現(xiàn)在版本基本上可以實(shí)現(xiàn) 100% 的代碼兼容性度帮,這意味著使用 kotlin 開發(fā)的項(xiàng)目可以無縫調(diào)用已有的 Java庫和代碼歼捏,設(shè)置可以再一個(gè)項(xiàng)目中使用 Java 和 Kotlin 混合編譯。當(dāng)然作為一個(gè) IDE 開發(fā)公司笨篷,Intellij IDEA 和 Android Studio 對 Kotlin 的支持非常完善瞳秽,設(shè)置可以一鍵轉(zhuǎn)換代碼
多平臺開發(fā):JetBrains 開發(fā) Kotlin 不僅僅想要取代 Java (Android開發(fā) —— 官方宣布 Kotlin 正式成為 Android 開發(fā)的語言,Web開發(fā) —— 對 Spring 框架的支持率翅,以及可以編譯生成 JavaScript 模塊)练俐,更遠(yuǎn)大的目標(biāo)使實(shí)現(xiàn)多平臺的統(tǒng)一,甚至可以進(jìn)行 Native 開發(fā)冕臭,基于 LLVM 底層虛擬機(jī)的實(shí)現(xiàn)腺晾,Kotlin 可以為各個(gè)平臺編寫原生應(yīng)用,在不久的將來可以看到 iOS 開發(fā)也有 Kotlin 的一席之地
簡潔安全:Kotlin 的入門相當(dāng)簡單辜贵,當(dāng)然你有 Java 基礎(chǔ)的話悯蝉,你可以十分清晰的感受到這門語言在 Java 的基礎(chǔ)上做了多少優(yōu)化提升,非常值得一試托慨,語法的定義與蘋果推出的 Swift 有些類似鼻由,http://nilhcem.com/swift-is-like-kotlin 上可以看到兩者的比較
當(dāng)然后面也會介紹到一小部分 Kotlin 的特性
kotlin 的語法糖
安全性
空安全
Java 的 NullPointException 一直以來都是導(dǎo)致程序崩潰的頭號殺手。kotlin 則通過類型系統(tǒng)旨在代碼中消除潛在的空指針安全問題。
在 kotlin 中類型系統(tǒng)會區(qū)分一個(gè)引用可以容納 null (可空引用)還是不能容納(非空引用)蕉世,通過在類名后添加后綴 蔼紧?
實(shí)現(xiàn)
var str1:String = "abc"
// str1 = null
// String是非空引用,不可以容納 null狠轻,編譯器會報(bào)錯(cuò)
如果需要允許賦值為空奸例,那么我們需要聲明該變量為可空引用,即 Srting?
var str2: String? = "abc"
str2 = null // 編譯通過
對于前者哈误,因?yàn)槭欠强找昧ㄖ粒覀兛梢灾苯诱{(diào)用對象屬性而不必考慮空指針的情形
val l = a.length
而對于后者,因?yàn)槭强煽找妹圩裕覀兙筒荒苤苯尤フ{(diào)用對象屬性菩貌,因?yàn)楹苡锌赡軙?dǎo)致空指針的出現(xiàn),因此 kotlin 直接杜絕了這種不安全的調(diào)用方式的出現(xiàn)
// val l2 = str2.length
// 編譯錯(cuò)誤:變量 str2 可能為空
安全調(diào)用操作符 ?.
針對可空引用重荠,kotlin 提供了專門的空安全調(diào)用操作符 ?.
// var l2: Int = str2?.length
// 編譯錯(cuò)誤:變量 l2 可能為空
var l2: Int? = str2?.length
如果變量 b 為 null箭阶,那么返回的也將是 null, 反之則會返回 b.length 的數(shù)值戈鲁,所以我們得到的結(jié)果類型也是將一個(gè)可空引用 Int?
這個(gè)操作符將在鏈?zhǔn)秸{(diào)用時(shí)發(fā)揮巨大的作用
a?.b?.c?.d // 這種形式的鏈?zhǔn)秸{(diào)用很有可能在一些復(fù)雜的數(shù)據(jù)結(jié)構(gòu)中有用到
當(dāng)鏈?zhǔn)秸{(diào)用的任一節(jié)點(diǎn)為 null 都會中斷鏈子返回 null仇参,而在 Java 代碼中
if(a != null) {
if(b != null) {
if(c != null) {
return c.d;
}
}
}
return null;
這樣的寫法即不簡潔也不美觀,而且最重要的一點(diǎn)是部分開發(fā)者并不會想到這么做
當(dāng)然要只對非空值執(zhí)行某個(gè)操作婆殿,安全調(diào)用操作符可以與 let 一起使用
val listWithNulls: List<String?> = listOf("A", null)
for (item in listWithNulls) {
item?.let { println(it) } // 輸出 A 并忽略了 null
}
Elvis 操作符 ?:
當(dāng)一個(gè)可空引用 b 需要如下操作诈乒,當(dāng) b 不為空時(shí)婆芦,我們使用 b,否則我們需要使用一個(gè)非空的默認(rèn)值 c 時(shí)消约,我們就可以使用該操作符
val l = b?.length ?: -1 // 相當(dāng)于 val l: Int = if (b != null) b.length else -1
如果 ?: 左側(cè)表達(dá)式非空,就返回其左側(cè)表達(dá)式或粮,否則返回右側(cè)表達(dá)式,并且只有當(dāng)左側(cè)的表達(dá)式為空時(shí)才會對右側(cè)的表達(dá)式求值
可能導(dǎo)致空指針的操作符 !!
對于一個(gè)可空引用的變量 b 來說氯材,使用該操作符相當(dāng)于返回一個(gè)非空引用的變量 b!!,可以直接使用對象的屬性氢哮,當(dāng)然如果 b 為 null,那么就會拋出 NullPointException 了
val l = b!!.length // 雖然 b 是可空類型命浴,但是可以調(diào)用獲取 length 屬性贱除,但是可能會拋出空指針異常
在 kotlin 中除非是你顯示的要求 NullPointException:
- 顯式調(diào)用 throw NullPointerException()
- 使用 !! 操作符
否則它不會不期而至媳溺,當(dāng)然還是有兩個(gè)導(dǎo)致空指針異常的原因:
- 外部 Java 代碼導(dǎo)致
- 對于初始化,有一些數(shù)據(jù)不一致(如一個(gè)未初始化的 this 用于構(gòu)造函數(shù)的某個(gè)地方)
類型轉(zhuǎn)換安全
如果你檢查類型是正確的悬蔽,編譯器會為你做自動類型轉(zhuǎn)換,kotlin 通過 is
操作符及其否定形式 !is
來檢查對象是否符合給定的類型蝎困,相當(dāng)于 Java 中的 instanceof 關(guān)鍵字
同時(shí)我們可以省略顯式的轉(zhuǎn)換操作录语,因?yàn)榫庉嬈鞲櫜豢勺冎档?is 檢查,并在需要是自動插入安全的轉(zhuǎn)換
if(x is String){
println(x.length)// x會被編譯器自動轉(zhuǎn)化為 Kotlin.String 類型禾乘,在編譯器上會有高亮提示
}
//安全轉(zhuǎn)換也可對反向檢查智能判定
if(x !is String){
return
}
println(x.length)
//安全轉(zhuǎn)換還可以實(shí)現(xiàn)對 && 和 || 操作符的兼容
if(x is String && x.length > 0) return//對于 && 后面數(shù)據(jù)類型自動轉(zhuǎn)換
if(x !is String || x.length == 0) return//對于 || 后面數(shù)據(jù)類型自動轉(zhuǎn)換
kotlin 也有顯式的轉(zhuǎn)換操作符 as
澎埠,如果對象不是目標(biāo)類型,那么常規(guī)類型轉(zhuǎn)換可能會導(dǎo)致 ClassCastException
//類型不匹配始藕,會拋出異常
val x: String = y as String
//上述代碼中蒲稳,如果 y 為空,也會拋出異常伍派,所以必須對空安全兼容
val x: String? = y as String?
當(dāng)然江耀,這種不安全的調(diào)用形式 kotlin 并不提倡,類似于空安全操作符诉植,kotlin 提供了一種安全的轉(zhuǎn)換操作符 as?
val x:String? = y as? String //如果類型不匹配祥国,則返回 null,所以 x 必須是可空變量晾腔,此時(shí)強(qiáng)轉(zhuǎn)類型就不必是可空類型了
簡潔性
高級類
Kotlin 提供了許多高級方法類系宫,目的就是為了簡化編程復(fù)雜度
data class 數(shù)據(jù)類
Kotlin 將 Java 中專門用于存放數(shù)據(jù)的類用 data 關(guān)鍵字標(biāo)記
data class User(val name: String, val age: Int)
數(shù)據(jù)類會由編譯器自動從主構(gòu)造函數(shù)中聲明的所有屬性導(dǎo)出以下成員
equals()/hashCode()
toString() 格式是 "User(name=John, age=42)"
-
componentN() 解構(gòu)聲明 按聲明順序?qū)?yīng)于所有屬性
val name = person.component1() val age = person.component2()
componentN() 函數(shù)運(yùn)用于解構(gòu)聲明,一種類似于 python 里元組的操作方式建车,每個(gè)對應(yīng)的屬性都有一個(gè)對應(yīng)的函數(shù),按順序疊加椒惨,是 kotlin 廣泛使用的約定原則
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // 輸出 "Jane, 35 years of age"
在上述代碼中缤至,name 和 age 是調(diào)用 component1() 和 component2() 的返回值
- copy() 函數(shù)
復(fù)制函數(shù)應(yīng)用于快速生成只有部分屬性不同的相似對象,其實(shí)現(xiàn)類似于
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
我們可以實(shí)現(xiàn)
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
當(dāng)然康谆,這種方式是淺拷貝
object 單例修飾關(guān)鍵字
單例類可以由關(guān)鍵字 object 修飾實(shí)現(xiàn)领斥,直接調(diào)用即可
順帶一提,Kotlin 并沒有提供靜態(tài)方法和屬性沃暗,我們需要通過伴生對象 companion object 實(shí)現(xiàn)靜態(tài)調(diào)用
enum class 枚舉類和 sealed class 密封類
枚舉類(enum class)與 Java 中的枚舉差距不大月洛,密封類(sealed class)在某種程度上是枚舉類的一種擴(kuò)展,和枚舉一樣孽锥,密封類的值是有限集中的類型嚼黔、而不能有任何其他類型细层,只是每個(gè)枚舉常量只存在一個(gè)實(shí)例,而密封類的一個(gè)子類可以有可包含狀態(tài)的多個(gè)實(shí)例
Type.extension 函數(shù)擴(kuò)展和屬性擴(kuò)展
擴(kuò)展一個(gè)類的新功能而無需繼承該類或使用像裝飾者這樣的任何類型的設(shè)計(jì)模式唬涧。這通過叫做擴(kuò)展的特殊聲明完成
// 擴(kuò)展方法不可以覆蓋原有方法
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // “this”對應(yīng)該列表
this[index1] = this[index2]
this[index2] = tmp
}
// 擴(kuò)展方法調(diào)用
val list = mutableListOf(1, 2, 3)
list.swap(0, 1)
println(list)
// 輸出結(jié)果: [2, 1, 3]
// 擴(kuò)展屬性不可以設(shè)值操作
val Int.isOdd: Boolean
get() = this and 1 == 1
// 擴(kuò)展屬性調(diào)用
val n = 3
println(n.isOdd)
// 輸出結(jié)果: true
對于傳統(tǒng)的工具類完全可以是用擴(kuò)展的方法代替疫赎,而且 ide 提供了智能聯(lián)想功能碎节,Android 開發(fā)中的 kotlin-android-extensions 庫也是利用了這個(gè)原理
操作符重載
可以先從相等性說起,Kotlin 中有兩種類型的相等性:
- 引用相等(兩個(gè)引用指向同一對象)
引用相等由 ===
(以及其否定形式 !==
)操作判斷胎撇。 a === b 當(dāng)且僅當(dāng) a 和 b 指向同 一個(gè)對象時(shí)求值為 true
- 結(jié)構(gòu)相等(用 equals() 檢查)
結(jié)構(gòu)相等由 ==
(以及其否定形式 !=
)操作判斷晚树。
a == b 相當(dāng)于 a?.equals(b) ?: (b === null)
// 因?yàn)?== 可能會出現(xiàn) null == null 的情況受葛,所以會出現(xiàn)額外的空判斷
當(dāng) a == null 這種判斷會被直接翻譯成 a === null
Kotlin 允許我們?yōu)樽约旱念愋吞峁╊A(yù)定義的一組操作符的實(shí)現(xiàn)总滩。這些操作符具有固定的符號表示(如 + 或 *)和固定的優(yōu)先級。為實(shí)現(xiàn)這樣的操作符席函,我們?yōu)橄鄳?yīng)的類型(即二元操作符左側(cè)的類型和一元操作符的參數(shù)類型)提供了一個(gè)固定名字的成員函數(shù)或擴(kuò)展函數(shù)茂附。 重載操作符的函數(shù)需要用 operator 修飾符標(biāo)記
比如說一元的操作符 ++
营曼,a++ 和 ++a 都可以翻譯為 a.inc()愚隧,當(dāng)然也保留了前后綴的區(qū)別狂塘。
后綴 a++ 的過程是
- 把 a 的初始值存儲到臨時(shí)存儲 a0 中
- 把 a.inc() 結(jié)果賦值給 a
- 把 a0 作為表達(dá)式的結(jié)果返回
而前綴 ++a 則是
- 把 a.inc() 結(jié)果賦值給 a ,
- 把 a 的新值作為表達(dá)式結(jié)果返回妈踊。
可見 kotlin 對特定操作符有做額外的判斷廊营,不僅僅是調(diào)用重載方法,就比如說對 == 操作符的 null 判斷的額外處理
二元操作符 +
夹囚,a + b 類似于 a.plus(b) ...
這種重載方法在對象中以 operator 修飾荸哟。當(dāng)然瞬捕,如果對象沒有實(shí)現(xiàn)重載方法怎么辦?當(dāng)然是可以實(shí)現(xiàn)函數(shù)擴(kuò)展啊劣砍,只要記得帶上 operator
在這里還有更多更詳細(xì)的關(guān)于操作符重載的知識
https://www.kotlincn.net/docs/reference/operator-overloading.html
委托
類委托
委托模式是在 Android 源碼中廣泛使用的模式刑枝,認(rèn)為是實(shí)現(xiàn)繼承的一個(gè)很好的代替模式装畅,當(dāng)無法或不想直接訪問某個(gè)對象時(shí)就可以通過一個(gè)代理對象來間接訪問掠兄。Kotlin 可以零樣板代碼地原生支持 它锌雀。 類 Derived 可以繼承一個(gè)接口 Base 腋逆,并將其所有共有的方法委托給一個(gè)指定的對象:
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
fun main(args: Array<String>) {
val b = BaseImpl(10)
Derived(b).print() // 輸出 10
}
by
關(guān)鍵字聲明了 Derived 中將存儲 b 變量惩歉,并且編譯器將會把所有的 Base 接口方法都轉(zhuǎn)發(fā)到 b 中執(zhí)行
屬性委托
Kotlin 同時(shí)支持屬性的委托
語法:val/var <property name>: <Type> by <expression>
- val/var:屬性類型(可變/只讀)
- name:屬性名稱
- Type:屬性的數(shù)據(jù)類型
- expression:代理類
在 by
關(guān)鍵字后面的表達(dá)式是具體代理類, 因?yàn)閷傩詫?yīng)的 getter ( 對于可變屬性來說還有 setter 方法 ) 會被委托給它的 getValue() 和 setValue() 方法。 屬性的委托不必實(shí)現(xiàn)任何的接口锨并,但是需要提供一個(gè) getValue() 函數(shù)( 和 setValue() )睬棚。 例如:
class Example {
var p: String by Delegate()
}
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name} in $thisRef.'")
}
}
如此的話,p 變量的賦值與取值都會被代理了撵摆。
當(dāng)我們從委托到一個(gè) Delegate 實(shí)例的 p 讀取時(shí)害晦,將調(diào)用 Delegate 中的 getValue() 函 數(shù)壹瘟, 所以它第一個(gè)參數(shù)是讀出 p 的對象、第二個(gè)參數(shù)保存了對 p 自身的描述 (例如你可以取它的名字)
val e = Example()
println(e.p)
輸出結(jié)果:
Example@33a17727, thank you for delegating ‘p’ to me!
類似地灵莲,當(dāng)我們給 p 賦值時(shí)政冻,將調(diào)用 setValue() 函數(shù)明场。前兩個(gè)參數(shù)相同询筏,第三個(gè)參數(shù)保存 將要被賦 的值
e.p = "NEW"
輸出結(jié)果:
NEW has been assigned to ‘p’ in Example@33a17727.
當(dāng)然 kotlin 中標(biāo)準(zhǔn)庫中包含了可以實(shí)現(xiàn)包含所需 operator 方法的 ReadOnlyProperty(val) 或 ReadWriteProperty(var) 接口嫌套,我們只需要繼承實(shí)現(xiàn)對應(yīng)方法即可
interface ReadOnlyProperty<in R, out T> {
operator fun getValue(thisRef: R, property: KProperty<*>): T
}
interface ReadWriteProperty<in R, T> {
operator fun getValue(thisRef: R, property: KProperty<*>): T
operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
另外 kotlin 標(biāo)準(zhǔn)庫提供了幾種常用方法的工廠方法
- 延遲屬性(lazy properties): 其值只在首次訪問時(shí)計(jì)算
- 可觀察屬性(observable properties): 監(jiān)聽器會收到有關(guān)此屬性變更的通知
- 把多個(gè)屬性儲存在一個(gè)映射(map)中踱讨,而不是每個(gè)存在單獨(dú)的字段中。
用過的人都說“真TM爽”
如果你想從 Java 的角度去看 kotlin 的語法糖莺治,那么你可以通過以下方法:
在 Intellij IDEA 或者 Android Studio 中選擇 .kt 文件谣旁,然后通過 tools -> Kotlin -> Show Koltin ByteCode 彈窗查看編譯后的字節(jié)碼滋早,你可以通過 Decompile 按鈕查看 Java 代碼