Kotlin 高阶语法解析
Kotlin 高级语法深度解析
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() } // 立即启动异步任务1
val deferred2 = async { fetchData2() } // 立即启动异步任务2
deferred1.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.Loading
try {
// 切换到 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) = person
println("$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 b
fun main() {
val b = BaseImpl(10)
Derived(b).print() // 输出: 10
}
8.2 属性委托
最常用的是 lazy 和 observable
import kotlin.properties.Delegates
class 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! 然后输出: Hello
println(e.lazyValue) // 第二次访问,直接输出: Hello
e.observedValue = "first" // 输出: <no name> -> first
e.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_user
bind { 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+5
println(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 仍然在原始的上下文中。
更多推荐
所有评论(0)