Kotlin入坑指南
帶你走進(jìn)Android Jetpack組件庫
Jetpack使用(一)之 Lifecycles 篇
Jetpack使用(二)之 LiveData 篇

前言
每個Android開發(fā)者都應(yīng)該學(xué)Kotlin
推薦學(xué)習(xí)Kotlin的理由有很多,比如:相比較于Java來說,Kotlin更簡潔,Kotlin 有協(xié)程,Kotlin有擴(kuò)展函數(shù),Kotlin更安全,Kotlin開發(fā)效率更快...
不過,如果你是Android開發(fā)者的話,我勸你還是別再做無謂的掙扎了,趕緊入坑吧!
快速認(rèn)識Kotlin
Kotlin 是著名 IDE 公司 JetBrains 創(chuàng)造出的一門基于 JVM 的語言。有著以下幾個特點:
-簡潔:代碼量大大減少
-安全:主要指“空安全”
-兼容:與Java兼容,kotlin與Java完美互調(diào)
-開發(fā)工具友好:IntelliJ對kotlin簡直不要太友好
JetBrains不僅創(chuàng)造了 Kotlin,還創(chuàng)造了著名的 IntelliJ IDEA。Android 開發(fā)者使用的 Android Studio 就是基于 IntelliJ 改造出來的。
基礎(chǔ)語法
1.所有 Kotlin 類都是對象 (Everything in Kotlin is an object)
與 Java 不一樣是:Kotlin 沒有基本數(shù)據(jù)類型 (Primitive Types),所有 Kotlin 里面的類都是對象,它們都繼承自: Any這個類;與 Java 類似的是,Kotlin 提供了如下的內(nèi)置類型:
| Type | Bit Width | 備注 |
|---|---|---|
| Double | 64 | Kotlin 沒有 double |
| Float | 32 | Kotlin 沒有 float |
| Long | 64 | Kotlin 沒有 long |
| Int | 32 | Kotlin 沒有 int/Integer |
| Short | 16 | Kotlin 沒有 short |
| Byte | 8 | Kotlin 沒有 byte |
2.可見性修飾符 (Visibility Modifiers)
| 修飾符 | 描述 |
|---|---|
| public | 與Java一致 |
| private | 與Java一致 |
| protected | 與Java一致 |
| internal | 同 Module 內(nèi)可見 |
3. 變量定義 (Defining Variables)
定義一個 Int 類型的變量:
var a: Int = 1
定義一個 Int 類型的常量(不可變的變量,只讀的變量)
val b: Int = 1
類型可推導(dǎo)時,類型申明可省略:
val c = 1
語句末尾的;可有可無:
val d: Int;
d = 1;
小結(jié):
- var定義變量
- val定義常量(不可變的變量,只讀變量)
- Kotlin 支持類型自動推導(dǎo)
4.空安全 (Null Safety)
定義一個可為空的 String 變量:
var b: String? = "Kotlin"
b = null
print(b)
// 輸出 null
定義一個不可為空的 String 變量:
var a: String = "Kotlin"
a = null
// 編譯器報錯,null 不能被賦給不為空的變量
變量賦值:
var a: String? = "Kotlin"
var b: String = "Kotlin"
b = a // 編譯報錯,String? 類型不可以賦值給 String 類型
a = b // 編譯通過
空安全調(diào)用
var a: String? = "Kotlin"
print(a.length) // 編譯器報錯,因為 a 是可為空的類型
print(a?.length) // 使用?. 的方式調(diào)用,輸出 null
Elvis 操作符
// 下面兩個語句等價
val l: Int = if (b != null) b.length else -1
val l = b?.length ?: -1
// Elvis 操作符在嵌套屬性訪問時很有用
val name = userInstance?.user?.baseInfo?.profile?.name?: "Kotlin"
小結(jié):
- T 代表不可為空類型,編譯器會檢查,保證不會被 null 賦值
- T? 代表可能為空類型
- 不能將 T? 賦值給 T
- 使用 instance?.fun() 進(jìn)行空安全調(diào)用
- 使用 Elvis 操作符為可空變量替代值,簡化邏輯
5.類型檢查與轉(zhuǎn)換 (Type Checks and Casts)
類型判斷、智能類型轉(zhuǎn)換:
if (x is String) {
print(x.length) // x 被編譯自動轉(zhuǎn)換為 String
}
// x is String 類似 Java 里的 instanceOf
不安全的類型轉(zhuǎn)換 as
val y = null
val x: String = y as String
//拋異常,null 不能被轉(zhuǎn)換成 String
安全的類型轉(zhuǎn)換 as?
val y = null
val z: String? = y as? String
print(z)
// 輸出 null
小結(jié):
- 使用 is 關(guān)鍵字進(jìn)行類型判斷
- 使用 as 進(jìn)行類型轉(zhuǎn)換,可能會拋異常
- 使用 as? 進(jìn)行安全的類型轉(zhuǎn)換
6. if 判斷
基礎(chǔ)用法跟 Java 一毛一樣。它們主要區(qū)別在于:Java If is Statement,Kotlin If is Expression。因此它對比 Java 多了些“高級”用法,懶得講了,咱看后面的實戰(zhàn)吧。
7.for 循環(huán)
跟 Java 也差不多,直接看代碼吧:
// 集合遍歷,跟 Java 差不多
for (item in collection) {
print(item)
}
// 辣雞 Kotlin 語法
for (item in collection) print(item)
// 循環(huán) 1,2,3
for (i in 1..3) {
println(i)
}
// 6,4,2,0
for (i in 6 downTo 0 step 2) {
println(i)
}
8. when
when 就相當(dāng)于高級版的 switch,它的高級之處在于支持模式匹配(Pattern Matching):
val x = 9
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
is String -> print("x is String")
x.isOdd() -> print("x is odd")
else -> print("none of the above")
}
// 輸出:x is in the range
9. 相等性 (Equality)
Kotlin 有兩種類型的相等性:
- 結(jié)構(gòu)相等 (Structural Equality)
- 引用相等 (Referential Equality)
結(jié)構(gòu)相等:
// 下面兩句兩個語句等價
a == b
a?.equals(b) ?: (b === null)
// 如果 a 不等于 null,則通過 equals 判斷 a、b 的結(jié)構(gòu)是否相等
// 如果 a 等于 null,則判斷 b 是不是也等于 null
引用相等:
print(a === b)
// 判斷 a、b 是不是同一個對象
10. 函數(shù) (Functions)
fun triple(x: Int): Int {
return 3 * x
}
// 函數(shù)名:triple
// 傳入?yún)?shù):不為空的 Int 類型變量
// 返回值:不為空的 Int 類型變量
11. 類 (Classes)
類定義
使用主構(gòu)造器(Primary Constructor)定義類一個 Person 類,需要一個 String 類型的變量:
class Person constructor(firstName: String) { ... }
如果主構(gòu)造函數(shù)沒有注解或者可見性修飾符,constructor 關(guān)鍵字可省略:
class Person(firstName: String) { ... }
也可以使用次構(gòu)造函數(shù)(Secondary Constructor)定義類:
class Person {
constructor(name: String) { ... }
}
// 創(chuàng)建 person 對象
val instance = Person("Kotlin")
init 代碼塊
Kotlin 為我們提供了 init 代碼塊,用于放置初始化代碼:
class Person {
var name = "Kotlin"
init {
name = "I am Kotlin."
println(name)
}
constructor(s: String) {
println(“Constructor”)
}
}
fun main(args: Array<String>) {
Person("Kotlin")
}
以上代碼輸出結(jié)果為:
I am Kotlin.
Constructor
結(jié)論:init 代碼塊執(zhí)行時機在類構(gòu)造之后,但又在“次構(gòu)造器”執(zhí)行之前。
12. 繼承 (Inheritance)
- 使用 open 關(guān)鍵字修飾的類,可以被繼承
- 使用 open 關(guān)鍵字修飾的方法,可以被重寫
- 沒有 open 關(guān)鍵字修飾的類,不可被繼承
- 沒有 open 關(guān)鍵字修飾的方法,不可被重寫
- 以 Java 的思想來理解,Kotlin 的類和方法,默認(rèn)情況下是 final 的
定義一個可被繼承的 Base 類,其中的 add() 方法可以被重寫,test() 方法不可被重寫:
open class Base {
open fun add() { ... }
fun test() { ... }
}
定義 Foo 繼承 Base 類,重寫 add() 方法
class Foo() : Base() {
override fun add() { ... }
}
- 使用 : 符號來表示繼承
- 使用 override 重寫方法
13. This 表達(dá)式 (Expression)
class A {
fun testA(){ }
inner class B { // 在 class A 定義內(nèi)部類 B
fun testB(){ }
fun foo() {
this.testB() // ok
this.testA() // 編譯錯誤
this@A.testA() // ok
this@B.testB() // ok
}
}
}
小結(jié):
- inner 關(guān)鍵字定義內(nèi)部類
- 在內(nèi)部類當(dāng)中訪問外部類,需要顯示使用 this@OutterClass.fun() 的語法
14. 數(shù)據(jù)類 (Data Class)
假設(shè)我們有個這樣一個 Java Bean:
public class Developer {
private String name;
public Developer(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Developer developer = (Developer) o;
return name != null ? name.equals(developer.name) : developer.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
return result;
}
@Override
public String toString() {
return "Developer{" + "name='" + name + '}';
}
}
如果我們將其翻譯成 Kotlin 代碼,大約會是這樣的:
class Developer(var name: String?) {
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o == null || javaClass != o.javaClass) return false
val developer = o as Developer?
return if (name != null) name == developer!!.name else developer!!.name == null
}
override fun hashCode(): Int {
return if (name != null) name!!.hashCode() else 0
}
override fun toString(): String {
return "Developer{" + "name='" + name + '}'.toString()
}
}
然而,Kotlin 為我們提供了另外一種選擇,它叫做數(shù)據(jù)類:
data class Developer(var name: String)
上面這一行簡單的代碼,完全能替代前面我們的寫的那一大堆模板 Java 代碼,甚至額外多出了一些功能。如果將上面的數(shù)據(jù)類翻譯成等價的 Java 代碼,大概會長這個樣子:
public final class Developer {
@NotNull
private String name;
public Developer(@NotNull String name) {
super();
this.name = name;
}
@NotNull
public final String getName() { return this.name; }
public final void setName(@NotNull String var1) { this.name = var1; }
@NotNull
public final Developer copy(@NotNull String name) { return new Developer(name); }
public String toString() { return "Developer(name=" + this.name + ")"; }
public int hashCode() { return this.name != null ? this.name.hashCode() : 0; }
public boolean equals(Object var1) {
if (this != var1) {
if (var1 instanceof Developer) {
Developer var2 = (Developer)var1;
if (Intrinsics.areEqual(this.name, var2.name)) {
return true;
}
}
return false;
} else {
return true;
}
}
}
可以看到,Kotlin 的數(shù)據(jù)類不僅為我們提供了 getter、setter、equals、hashCode、toString,還額外的幫我們實現(xiàn)了 copy 方法!這也體現(xiàn)了 Kotlin 的簡潔特性。
序列化的坑
如果是舊工程遷移到 Kotlin,那么可能需要注意這個坑:
// 定義一個數(shù)據(jù)類,其中成員變量 name 是不可為空的 String 類型,默認(rèn)值是 Kotlin
data class Person(val age: Int, val name: String = "Kotlin")
val person = gson.fromJson("""{"age":42}""", Person::class.java)
print(person.name) // 輸出 null
對于上面的情況,由于 Gson 最初是為 Java 語言設(shè)計的序列化框架,并不支持 Kotlin 不可為空、默認(rèn)值這些特性,從而導(dǎo)致原本不可為空的屬性變成null,原本應(yīng)該有默認(rèn)值的變量沒有默認(rèn)值。
對于這種情,市面上已經(jīng)有了解決方案: