
你的支持對我意義重大!
?? Hi,我是旭銳。本文已收錄到 GitHub · Android-NoteBook 中。這里有 Android 進階成長路線筆記 & 博客,有志同道合的朋友,歡迎跟著我一起成長。(聯(lián)系方式 & 入群方式在 GitHub)
前言
- 在軟件開發(fā)領(lǐng)域,經(jīng)常會涌現(xiàn)出新的編程語言,但很少有新語言能夠像 Kotlin 一樣在如此短的時間內(nèi)成為最受歡迎的語言之一;
- 在 Android 生態(tài)中主要有 C++、Java、Kotlin 三種語言 ,它們的關(guān)系不是替換而是互補。其中,C++ 的語境是算法和高性能,Java 的語境是平臺無關(guān)和內(nèi)存管理,而 Kotlin 則融合了多種語言中的優(yōu)秀特性,帶來了一種更現(xiàn)代化的編程方式。 例如簡化異步編程的協(xié)程(coroutines),提高代碼質(zhì)量的可空性(nullability),lambda 表達(dá)式等;
- 在這篇文章里,我將討論 Kotlin 相較于 Java 的優(yōu)勢。另外,作為客戶端同學(xué),我還會討論 Kotlin 在 Android 開發(fā)中的應(yīng)用實踐。如果能幫上忙,請務(wù)必點贊加關(guān)注,這真的對我非常重要。
目錄
1. Kotlin 基礎(chǔ)
- == 和 equal() 相同,=== 比較內(nèi)存地址
-
頂級成員(函數(shù) & 屬性)的原理: Kotlin 頂級成員的本質(zhì)是 Java 靜態(tài)成員,編譯后會自動生成
文件名Kt的類,可以使用@Jvm:fileName注解修改自動生成的類名 -
默認(rèn)參數(shù)的原理: Kotlin 默認(rèn)參數(shù)的本質(zhì)是將默認(rèn)值 固化 到調(diào)用位置,所以在 Java 中無法直接調(diào)用帶默認(rèn)參數(shù)的函數(shù),需要在 Kotlin 函數(shù)上增加
@JvmOverloads注解,指示編譯器生成重載方法 - 解構(gòu)聲明的原理: Kotlin 解構(gòu)聲明可以把一個對象的屬性結(jié)構(gòu)為一組變量,所以解構(gòu)聲明的本質(zhì)是局部變量。
舉例:
val (name, price) = Book("Kotlin入門", 66.6f)
println(name)
println(price)
-------------------------------------------
Kotlin 類需要聲明`operator fun componentN()`方法來實現(xiàn)解構(gòu)功能,否則是不具備解構(gòu)聲明的功能的,例如:
class Book(var name: String, var price: Float) {
operator fun component1(): String { // 解構(gòu)的第一個變量
return name
}
operator fun component2(): Float { // 解構(gòu)的第二個變量
return price
}
}
- 擴展函數(shù)的原理: 擴展函數(shù)的語義是在不修改類 / 不繼承類的情況下,向一個類添加新函數(shù)或者新屬性。本質(zhì)是靜態(tài)函數(shù),靜態(tài)函數(shù)的第一個參數(shù)是接收者類型,調(diào)用擴展時不會創(chuàng)建適配對象或者任何運行時的額外消耗。在 Java 中,我們只需要像調(diào)用普通靜態(tài)方法那樣調(diào)用擴展即可。相關(guān)深入文章:《Kotlin | 擴展函數(shù)(終于知道為什么 with 用 this,let 用 it)》
- 委托機制的原理: Kotlin 委托的語法關(guān)鍵字是 by,其本質(zhì)上是面向編譯器的語法糖,三種委托(類委托、對象委托和局部變量委托)在編譯時都會轉(zhuǎn)化為 “無糖語法”。例如類委托:編譯器會實現(xiàn)基礎(chǔ)接口的所有方法,并直接委托給基礎(chǔ)對象來處理。例如對象委托和局部變量委托:在編譯時會生成輔助屬性(prop$degelate),而屬性 / 變量的 getter() 和 setter() 方法只是簡單地委托給輔助屬性的 getValue() 和 setValue() 處理。相關(guān)深入文章:《Kotlin | 委托機制 & 原理 & 應(yīng)用》
- let、with、apply 的區(qū)別和應(yīng)用場景:
- 中綴函數(shù): 聲明 infix 關(guān)鍵字的函數(shù)是中綴函數(shù),可以使用中綴表示法調(diào)用(忽略點和括號)
中綴函數(shù)的要求:
- 1、成員函數(shù)或擴展函數(shù)
- 2、函數(shù)只有一個參數(shù)
- 3、不能使用可變參數(shù)或默認(rèn)參數(shù)
舉例:
infix fun String.吃(fruit: String): String {
return "${this}吃${fruit}"
}
調(diào)用: "小明" 吃 "蘋果"
類型系統(tǒng)
- 數(shù)值類型: Kotlin 將基本數(shù)據(jù)類型和引用型統(tǒng)一為:Byte、Short、Int、Long、Float、Double、Char 和 Boolean。需要注意的是,類型的統(tǒng)一并不意味著 Kotlin 所有的數(shù)值類型都是引用類型,大多數(shù)情況下,它們在編譯后會變成基本數(shù)據(jù)類型,類型參數(shù)會被編譯為引用類型。
- 類型轉(zhuǎn)換: 較小類型并不是較大類型的子類型,較小的類型不能隱式轉(zhuǎn)換為較大的類型。
val b: Byte = 1 // OK, 字面值是靜態(tài)檢測的
val i: Int = b // 錯誤
val i: Int = b.toInt() // OK
- 只讀集合和可變集合: 只讀集合只可讀,而可變集合可以增刪該差(例如 List 只讀,MutableList 可變)。需要注意,只讀集合引用指向的集合不一定是不可變的,因為你使用的變量可能是眾多指向同一個集合的其中一個。
- Array 和 IntArray 的區(qū)別: Array<Int> 相當(dāng)于引用類型數(shù)組 Integer[],IntArray 相當(dāng)于數(shù)值類型數(shù)組 int[]。
面向?qū)ο?/h4>
-
類修飾符
Kotlin 類 / 方法默認(rèn)是 final 的,如果想讓繼承類 / 重寫方法,需要在基類 / 基方法添加 open 修飾符。
final:不允許繼承或重寫
open:允許繼承或重寫
abstract:抽象類 / 抽象方法
-
訪問修飾符
Java 默認(rèn)的訪問修飾符是 protected,Kotlin 默認(rèn)的訪問修飾符是 public。
public:所有地方可見
internal:模塊中可見,一個模塊就是一組編譯的 Kotlin 文件
protected:子類中可見(與 Java 不同,相同包不可見)
private:類中可見
內(nèi)部類
Kotlin:默認(rèn)為靜態(tài)內(nèi)部類,如果想訪問類中的成員方法和屬性,需要添加 inner 關(guān)鍵字稱為非靜態(tài)內(nèi)部類;
Java:默認(rèn)為非靜態(tài)內(nèi)部類。
object 與 companion object 的區(qū)別
object 有兩層語義:靜態(tài)匿名內(nèi)部類 + 單例對象
companion object 是伴生對象,一個類只能有一個,代表了類的靜態(tài)成員(函數(shù) / 屬性)
object 單例的原理
lambda 表達(dá)式
- lambda 表達(dá)式本質(zhì)上是 「可以作為值傳遞的代碼塊」。在老版本 Java 中,傳遞代碼塊需要使用匿名內(nèi)部類實現(xiàn),而使用 lambda 表達(dá)式甚至連函數(shù)聲明都不需要,可以直接傳遞代碼塊作為函數(shù)值。
- 當(dāng) lambda 表達(dá)式只有一個參數(shù),可以用 it 關(guān)鍵字來引用唯一的實參。
-
lambda 表達(dá)式的種類
1、普通 Lambda 表達(dá)式:例如 ()->R
2、帶接收者對象的 Lambda 表達(dá)式:例如 T.()->R
-
lambda 表達(dá)式訪問局部變量的原理: 在 Java 中,匿名內(nèi)部類訪問的局部變量必須是 final 修飾的,否則需要使用數(shù)組或?qū)ο笞鲆粚影b。在 Kotlin 中,lambda 表達(dá)式可以直接訪問非 final 的局部變量,其原理是提供了一層包裝類,修改局部變量本質(zhì)上是修改包裝類中的值。
class Ref<T>(var value:T)
-
lambda 編譯優(yōu)化: 在循環(huán)中使用 Java 8 與 Kotlin 中的 lambda 表達(dá)式時,會存在編譯時優(yōu)化,編譯器會將 lambda 優(yōu)化為一個 static 變量,除非 lambda 表達(dá)式中訪問了外部的變量或函數(shù)。
-
內(nèi)聯(lián)函數(shù)的原理: lambda 表達(dá)式編譯后會變成匿名內(nèi)部類,至少會生成一個中間對象,當(dāng) lambda 表達(dá)式被經(jīng)常調(diào)用時,會增大運行開銷。使用內(nèi)聯(lián)函數(shù)可以減少中間對象的開銷,因為調(diào)用內(nèi)聯(lián)函數(shù)不會真正調(diào)用函數(shù),而是把函數(shù)實現(xiàn)固化到函數(shù)調(diào)用的位置。需要注意:如果函數(shù)體太大就不適合使用內(nèi)聯(lián)函數(shù)了,因為會大幅度增加字節(jié)碼大小。
-
實化類型參數(shù) reified: 因為泛型擦除的影響,運行期間不清楚類型實參的時機類型,Kotlin 中使用 帶實化類型參數(shù)的內(nèi)聯(lián)函數(shù) 可以突破這種限制,實化類型參數(shù)在插入到調(diào)用位置時會使用類型實參的確切類型代替,因此可以確定實參類型。
在這個函數(shù)里,我們傳入一個List,企圖從中過濾出 T 類型的元素:
Java:
<T> List<T> filter(List list) {
List<T> result = new ArrayList<>();
for (Object e : list) {
if (e instanceof T) { // compiler error
result.add(e);
}
}
return result;
}
---------------------------------------------------
Kotlin:
fun <T> filter(list: List<*>): List<T> {
val result = ArrayList<T>()
for (e in list) {
if (e is T) { // cannot check for instance of erased type: T
result.add(e)
}
}
return result
}
調(diào)用:
val list = listOf("", 1, false)
val strList = filter<String>(list)
---------------------------------------------------
內(nèi)聯(lián)后:
val result = ArrayList<String>()
for (e in list) {
if (e is String) {
result.add(e)
}
}
3. 協(xié)程
協(xié)程
-
協(xié)程的原理: 使用同步代碼編寫異步程序
4. 框架
Kotlin 類 / 方法默認(rèn)是 final 的,如果想讓繼承類 / 重寫方法,需要在基類 / 基方法添加 open 修飾符。
final:不允許繼承或重寫
open:允許繼承或重寫
abstract:抽象類 / 抽象方法
Java 默認(rèn)的訪問修飾符是 protected,Kotlin 默認(rèn)的訪問修飾符是 public。
public:所有地方可見
internal:模塊中可見,一個模塊就是一組編譯的 Kotlin 文件
protected:子類中可見(與 Java 不同,相同包不可見)
private:類中可見
內(nèi)部類
Kotlin:默認(rèn)為靜態(tài)內(nèi)部類,如果想訪問類中的成員方法和屬性,需要添加 inner 關(guān)鍵字稱為非靜態(tài)內(nèi)部類;
Java:默認(rèn)為非靜態(tài)內(nèi)部類。
object 與 companion object 的區(qū)別
object 有兩層語義:靜態(tài)匿名內(nèi)部類 + 單例對象
companion object 是伴生對象,一個類只能有一個,代表了類的靜態(tài)成員(函數(shù) / 屬性)
object 單例的原理
1、普通 Lambda 表達(dá)式:例如 ()->R
2、帶接收者對象的 Lambda 表達(dá)式:例如 T.()->R
class Ref<T>(var value:T)
在這個函數(shù)里,我們傳入一個List,企圖從中過濾出 T 類型的元素:
Java:
<T> List<T> filter(List list) {
List<T> result = new ArrayList<>();
for (Object e : list) {
if (e instanceof T) { // compiler error
result.add(e);
}
}
return result;
}
---------------------------------------------------
Kotlin:
fun <T> filter(list: List<*>): List<T> {
val result = ArrayList<T>()
for (e in list) {
if (e is T) { // cannot check for instance of erased type: T
result.add(e)
}
}
return result
}
調(diào)用:
val list = listOf("", 1, false)
val strList = filter<String>(list)
---------------------------------------------------
內(nèi)聯(lián)后:
val result = ArrayList<String>()
for (e in list) {
if (e is String) {
result.add(e)
}
}
Kotlin-Flow
5. 總結(jié)
最后,回顧下我們的 Kotlin 路線系列文章:
- Java | 關(guān)于泛型能問的都在這里了(含Kotlin)
- Kotlin | 擴展函數(shù)(終于知道為什么 with 用 this,let 用 it)
- Kotlin | 委托機制 & 原理 & 應(yīng)用
- Android | ViewBinding 與 Kotlin 委托雙劍合璧
參考資料
- Kotlin 官方文檔
- Android Kotlin 文檔
- 谷歌:選用 Kotlin 的五大理由 —— 劉志勇 譯
- Medium · 關(guān)于 Kotlin 的技術(shù)文章 —— Android 團隊
- Kotlin 實戰(zhàn) —— [俄] DmitryJeme 著
創(chuàng)作不易,你的「三連」是丑丑最大的動力,我們下次見!
