Kotlin 高級語法深度解析
- 1. 協程(Coroutines)
- 1.1 基礎概念
- 1.掛起和恢復
- 2.協程構建器 (Coroutine Builders)
- 3.協程作用域
- 4.調度器
- 1.2 核心用法
- 1.3 實戰示例
- 2. 密封類(Sealed Classes)
- 2.1 定義與特性
- 2.2 模式匹配
- 2.3 應用場景
- 3. 內聯函數(Inline Functions)
- 3.1 基礎用法
- 3.2 關鍵字擴展
- 4. 擴展函數(Extension Functions)
- 4.1 基礎定義
- 4.2 最佳實踐
- 5. 類型系統進階
- 5.1 空安全
- 5.2 泛型系統
- 6. 作用域函數(Scope Functions)
- 7. 數據類與解構
- 7.1 數據類特性
- 7.2 解構擴展
- 8. 委托 (Delegation)
- 8.1 類委托
- 8.2 屬性委托
- 9.高階函數 和 Lambda 表達式
- 10. 高級特性應用
- 10.1 DSL構建
- 10.2 反射與元編程
- 11.Flow 數據流處理
- 11.1創建 Flow
- 11.2 Flow 的生命周期操作符
- 11.3流上下文 (Context) 與 flowOn
1. 協程(Coroutines)
協程是 Kotlin 處理異步編程和并發的利器,它允許你以同步的代碼風格編寫異步邏輯,避免了回調地獄
1.1 基礎概念
1.掛起和恢復
暫停當前協程的執行,并釋放它占用的線程資源,讓線程去執行其他任務。當掛起的操作(如網絡請求返回)完成后,協程會在合適的線程上恢復執行。
// 聲明一個掛起函數
suspend fun fetchUserData(): User {// ... 執行耗時操作,如網絡請求return withContext(Dispatchers.IO) { // 切換到 IO 線程池執行// 模擬網絡請求delay(1000) // 這是一個掛起函數,非阻塞地延遲User("John") // 返回結果}
}
// 掛起函數只能在另一個掛起函數或協程中被調用
2.協程構建器 (Coroutine Builders)
用于啟動一個新的協程。
- launch: 啟動一個新協程,不返回結果。用于執行一段“一勞永逸”的工作(Fire-and-forget)。
fun main() = runBlocking {val job = launch { // 返回一個 Job 對象,用于管理協程delay(1000L)println("World!")}println("Hello,")job.join() // 等待協程執行完畢
}
// 輸出: Hello, (等待1秒) World!
- async: 啟動一個新協程,并返回一個 Deferred 對象(一個輕量級的、帶有結果的
Future)。用于并行執行任務并獲取結果,通常與 await() 一起使用。
suspend fun concurrentSum(): Int = coroutineScope {val deferred1 = async { fetchData1() } // 立即啟動異步任務1val deferred2 = async { fetchData2() } // 立即啟動異步任務2deferred1.await() + deferred2.await() // 等待兩個任務都完成并求和
}
3.協程作用域
通過coroutineScope、viewModelScope等管理生命周期
GlobalScope: 全局作用域,生命周期與應用程序一樣長。應謹慎使用,容易造成協程泄漏。
coroutineScope: 一個掛起函數,用于創建一個新的作用域,它會等待所有子協程完成后才完成自身。如果子協程失敗,它會取消所有其他子協程并傳播異常。
supervisorScope: 類似 coroutineScope,但子協程的失敗不會導致其他子協程取消( supervision )。適用于獨立的并行任務。
Android 中的生命周期感知作用域:
viewModelScope (在 ViewModel 中使用)
lifecycleScope (在 Activity/Fragment 中使用)
4.調度器
決定協程在哪個或哪些線程上執行
Dispatchers.Main: 在主線程(UI線程)上執行。用于更新 UI 和進行輕量級操作。
Dispatchers.IO: 專為磁盤和網絡 I/O 操作優化。使用共享的線程池。
Dispatchers.Default: 專為 CPU 密集型計算任務優化。使用共享的線程池,其大小與 CPU 核心數相同。
Dispatchers.Unconfined: 不限制任何特定線程。不推薦新手使用。
1.2 核心用法
// 結構化并發示例
viewModelScope.launch {try {val user = async { fetchUser() }val news = async { fetchNews() }updateUI(user.await(), news.await())} catch (e: Exception) {showError(e)}
}// 線程切換
suspend fun loadData() = withContext(Dispatchers.IO) {// 網絡請求
}
1.3 實戰示例
Android 中的典型用法
// 在 ViewModel 中
class MyViewModel : ViewModel() {// 使用 viewModelScope,當 ViewModel 被清除時自動取消所有協程fun loadUserData() {viewModelScope.launch { // 在主線程啟動_uiState.value = UiState.Loadingtry {// 切換到 IO 線程執行網絡請求和數據庫操作val userProfile = withContext(Dispatchers.IO) {// 并行執行兩個異步任務val userDeferred = async { api.getUser() }val postsDeferred = async { api.getPosts() }UserProfile(userDeferred.await(), postsDeferred.await())}// 回到主線程更新狀態_uiState.value = UiState.Success(userProfile)} catch (e: Exception) {// 回到主線程處理錯誤_uiState.value = UiState.Error(e.message)}}}
}
2. 密封類(Sealed Classes)
密封類用于表示受限的類繼承結構,當一個值只能是有限幾種類型之一時非常有用,常與 when 表達式結合使用,確保窮舉檢查。
2.1 定義與特性
sealed class Result<out T> {data class Success<T>(val data: T) : Result<T>()data class Error(val exception: Exception) : Result<Nothing>()object Loading : Result<Nothing>()
}
2.2 模式匹配
fun handleResult(result: Result<String>) {when (result) {is Result.Success -> println("Data: ${result.data}")is Result.Error -> println("Error: ${result.exception}")Result.Loading -> println("Loading...")}
}
2.3 應用場景
UI狀態管理(Idle/Processing/Success/Failure)
API響應處理(Success/Error/NetworkError)
3. 內聯函數(Inline Functions)
使用 inline 關鍵字修飾的函數,在編譯時會將其函數體直接插入到調用處,可以減少函數調用的開銷,尤其適用于接收 Lambda 作為參數的高階函數,可以避免 Lambda 對象的創建。
3.1 基礎用法
// 高階函數內聯優化
inline fun <T> measureTime(block: () -> T): T {val start = System.nanoTime()return block().also { println("Time: ${System.nanoTime() - start}") }
}// 使用示例
val result = measureTime {// 耗時操作
}
3.2 關鍵字擴展
noinline:禁止內聯特定lambda參數
crossinline:禁止lambda內部使用return
reified:具體化泛型類型參數
inline fun <reified T> Activity.openAct() {startActivity(Intent(this, T::class.java))
}
4. 擴展函數(Extension Functions)
4.1 基礎定義
// 為String添加反轉方法
fun String.reverse(): String {return this.reversed()
}// Android擴展
fun Context.showToast(message: String) {Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
4.2 最佳實踐
組織方式:按功能模塊分組擴展函數
作用域:優先擴展接口而非具體類
性能優化:避免過度擴展基礎類型
5. 類型系統進階
5.1 空安全
// 安全調用鏈
val length: Int? = text?.length
val safeLength = text?.length ?: 0// 非空斷言
val forcedLength = text!!.length
5.2 泛型系統
協變(out T):生產者角色
逆變(in T):消費者角色
類型投影:Array
Kotlin 使用聲明處型變,解決了 Java 通配符 (? extends T, ? super T) 的復雜性問題。
1.out (協變 Covariant):生產者,只能輸出(返回)T。Producer<out T> 是 Producer<U> 的子類型,如果 T 是 U 的子類型。類似于 Java 的 ? extends T。interface Producer<out T> {fun produce(): T // T 只出現在 out 位置
}
2.in (逆變 Contravariant):消費者,只能輸入(消耗)T。Consumer<in T> 是 Consumer<U> 的子類型,如果 T 是 U 的父類型。類似于 Java 的 ? super T。interface Consumer<in T> {fun consume(item: T) // T 只出現在 in 位置
}
6. 作用域函數(Scope Functions)
Kotlin 提供了幾個作用域函數:let, run, with, apply, also。它們的主要目的是在對象的上下文中執行代碼塊,并且各自有細微的差別(返回值和 this/it 的指代)。
函數 | 對象引用 | 返回值 | 適用場景 |
---|---|---|---|
let | it | lambda結果 | 對象為空時跳過操作 |
run | this | lambda結果 | 需要計算多個屬性時 |
with | this | lambda結果 | 配置對象參數 |
apply | this | 對象自身 | 對象初始化配置 |
also | it | 對象自身 | 對象副作用操作 |
示例:
// 對象初始化
val user = User().apply {name = "John"age = 30
}// 條件判斷
val result = data?.let { process(it) } ?: defaultValue
7. 數據類與解構
允許將一個對象的多個屬性或組件一次性賦值給多個變量。
7.1 數據類特性
data class User(val name: String, val age: Int)
自動生成equals()/hashCode()/toString()
支持copy()方法
解構聲明:val (name, age) = user
7.2 解構擴展
原理: 編譯器會調用對象的 component1(), component2() 等運算符函數。數據類(data class)會自動生成這些函數。
// 為現有類添加解構支持
data class Person(val name: String, val age: Int)fun main() {val person = Person("Alice", 29)// 解構聲明:根據主構造函數中聲明的屬性順序val (name, age) = personprintln("$name is $age years old") // 輸出: Alice is 29 years old// 對于集合也適用(因為 componentN() 函數)val (first, second, third) = listOf("a", "b", "c")println("$first, $second, $third") // 輸出: a, b, c
}
8. 委托 (Delegation)
Kotlin 原生支持委托模式,通過 by 關鍵字實現,可以將一個類的接口實現委托給另一個對象。
8.1 類委托
interface Base {fun print()
}class BaseImpl(val x: Int) : Base {override fun print() { print(x) }
}// Derived 類將 Base 接口的實現委托給 baseObject
class Derived(b: Base) : Base by bfun main() {val b = BaseImpl(10)Derived(b).print() // 輸出: 10
}
8.2 屬性委托
最常用的是 lazy 和 observable
import kotlin.properties.Delegatesclass Example {// 延遲初始化,第一次訪問時才計算val lazyValue: String by lazy {println("computed!")"Hello"}// 可觀察屬性,值改變時會觸發回調var observedValue: String by Delegates.observable("<no name>") {prop, old, new ->println("$old -> $new")}
}fun main() {val e = Example()println(e.lazyValue) // 第一次訪問,輸出: computed! 然后輸出: Helloprintln(e.lazyValue) // 第二次訪問,直接輸出: Helloe.observedValue = "first" // 輸出: <no name> -> firste.observedValue = "second" // 輸出: first -> second
}
9.高階函數 和 Lambda 表達式
高階函數是將函數用作參數或返回值的函數。Lambda 表達式是定義匿名函數的簡潔方式。
// 定義一個高階函數
fun calculate(x: Int, y: Int, operation: (Int, Int) -> Int): Int {return operation(x, y)
}fun main() {// 使用 Lambda 表達式val addResult = calculate(10, 5) { a, b -> a + b }println(addResult) // 輸出: 15// 使用函數引用 (::)val multiplyResult = calculate(10, 5, ::multiplyHelper)println(multiplyResult) // 輸出: 50// 另一個例子:使用集合的高階函數val numbers = listOf(1, 2, 3, 4, 5)val evenSquares = numbers.filter { it % 2 == 0 } // 過濾偶數.map { it * it } // 計算平方println(evenSquares) // 輸出: [4, 16]
}fun multiplyHelper(a: Int, b: Int) = a * b
要點:
it:如果 Lambda 只有一個參數,可以使用默認名稱 it。
最后一個 Lambda:如果函數的最后一個參數是 Lambda,它可以移到括號外面。如果它是唯一參數,括號可以省略。這是 Kotlin DSL 的基礎。
()->Unit:表示一個無參數無返回值的函數類型。
10. 高級特性應用
10.1 DSL構建
// RecyclerView DSL示例
recyclerView.build {layoutManager = LinearLayoutManager(context)adapter {itemType<User> {layoutRes = R.layout.item_userbind { holder, user ->holder.name.text = user.name}}}
}
10.2 反射與元編程
完整支持Java反射API
Kotlin反射庫:kotlin-reflect 在運行時動態地檢查、訪問和操作類、對象、屬性、函數等。功能強大,但性能開銷較大。
import kotlin.reflect.full.*data class Person(val name: String, var age: Int)fun main() {val person = Person("Alice", 29)val kClass = person::class // 獲取 KClass 對象// 檢查成員kClass.memberProperties.forEach { println(it.name) } // 輸出: name, age// 訪問屬性值val ageProperty = kClass.declaredMemberProperties.find { it.name == "age" }println(ageProperty?.get(person)) // 輸出: 29// 調用函數val kFunction = ::Person // 獲取構造函數引用val newPerson = kFunction.call("Bob", 30)println(newPerson) // 輸出: Person(name=Bob, age=30)
}
11.Flow 數據流處理
Flow 是 Kotlin 協程庫中用于處理異步數據流(Asynchronous Streams)的組件。它可以按順序發射多個值,而不是像 suspend 函數那樣只返回單個值。你可以把它想象成一個“異步序列”或“響應式流”,類似于 RxJava 的 Observable 或 Reactor 的 Flux。
11.1創建 Flow
最常用的創建方式是使用 flow { … } 構建器。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*// 創建一個簡單的 Flow,發射 1 到 3 三個數字
fun simpleFlow(): Flow<Int> = flow {println("Flow started")for (i in 1..3) {delay(1000) // 模擬一個異步操作,比如網絡請求emit(i) // 發射一個值到流中}
}suspend fun main() = runBlocking {// 第一次收集:會觸發 Flow 的執行println("Calling collect first time...")simpleFlow().collect { value -> println("Collected $value") }// 等待一段時間后再次收集delay(3000)// 第二次收集:會再次觸發一個全新的、獨立的執行println("Calling collect second time...")simpleFlow().collect { value -> println("Collected again $value") }
}
輸出:
Calling collect first time…
Flow started Collected 1 // 等待1秒后
Collected 2 // 再等待1秒后
Collected 3 // 再等待1秒后
Calling collect second
time… Flow started // 再次打印,證明是新的執行
Collected again 1
Collected again 2
Collected again 3
11.2 Flow 的生命周期操作符
Flow 提供了類似集合的操作符,但它們是中間操作符,返回一個新的 Flow,并且大多是冷的。
轉換操作符 (Transform Operators)
-
map: 將每個發射的值進行轉換。
-
filter: 過濾發射的值。
-
transform: 更通用的轉換,可以發射任意次數的值。
suspend fun main() = runBlocking {(1..5).asFlow() // 將集合轉換為 Flow.filter { it % 2 == 0 } // 過濾偶數.map { it * it } // 將偶數平方.collect { println(it) } // 收集結果:4, 16
}- 限長操作符 (Size-limiting Operators)
take: 只取前 N 個值,然后取消流的執行。
suspend fun main() = runBlocking {flow {try {emit(1)emit(2)println("This will not print")emit(3) // 因為 take(2),執行到這里之前流已被取消} finally {println("Finally in flow") // 仍然會執行,用于資源清理}}.take(2).collect { println(it) } // 輸出: 1, 2, Finally in flow
}
- 末端操作符 (Terminal Operators)
末端操作符是掛起函數,它啟動流的收集。最常見的末端操作符是 collect。 - collect: 收集所有發射的值。
- toList / toSet: 將流收集到集合中。
- first() / single(): 獲取第一個或唯一一個值。
- reduce / fold: 對流進行聚合操作。
suspend fun main() = runBlocking {val sum = (1..5).asFlow().reduce { accumulator, value -> accumulator + value } // 累加: 1+2+3+4+5println(sum) // 輸出: 15
}
11.3流上下文 (Context) 與 flowOn
Flow 的構建器代碼默認在收集器所在的協程上下文中運行。如果要改變流發射的上下文(例如,切換到 IO 線程進行網絡請求),需要使用 flowOn 操作符。
fun simpleFlow(): Flow<Int> = flow {println("Started in ${Thread.currentThread().name}") // 在 IO 線程執行for (i in 1..3) {delay(100)emit(i)}
}.flowOn(Dispatchers.IO) // 指定上游流的執行上下文suspend fun main() = runBlocking {println("Collecting in ${Thread.currentThread().name}") // 在主線程收集simpleFlow().collect { value ->println("$value collected in ${Thread.currentThread().name}") // 在主線程收集}
}
輸出:
text Collecting in main @coroutine#1
Started in DefaultDispatcher-worker-1 @coroutine#2
1 collected in main @coroutine#1
2 collected in main @coroutine#1
3 collected in main @coroutine#1
注意:flowOn 改變了它之前的操作符的上下文。收集操作 collect 仍然在原始的上下文中。