Kotlin / Compose 基础知识参考字典

Kotlin / Compose 字典。体例对齐站内 Go 关键字篇:每条给语义、多场景示例、边界与坑。

1. 空安全操作符

描述

?. 安全调用(左侧 null 直接返回 null);?: 兜底;!! 断言抛 NPE;as? 安全转换。Kotlin 把 null 编进类型系统,编译期就能拦。

语法和示例

var name: String? = null
val len = name?.length ?: 0              // 安全调用 + 兜底
val first = name?.firstOrNull()
name!!.length                             // 明知非空才用
// 安全转换
val n = obj as? Int                       // 失败返回 null
// let 作用域
name?.let { println(it) }
// 平台类型(Java 互操作)可能为 null,赋值给非空类型需 !! 或判空

注意事项

  • !! 抛的是 NullPointerException,且无堆栈指向调用点(难调试)
  • Java 返回值是「平台类型」String!,编译器不检查——与 Kotlin 非空类型交互时要判空
  • ?.let 里 it 恒非空;嵌套 let 注意命名冲突
  • Lateinit 只用于 var 且非空类型;访问前未初始化会抛 UninitializedPropertyAccessException

2. data class – 数据载体

描述

自动生成 equals/hashCode/toString/copy/componentN(解构)。字段建议全 val 保证稳定;超过 22 个参数会失去 copy 等生成(超出限制)。

语法和示例

data class User(val id: Int, val name: String) {
    val label: String get() = "#$id $name"   // 可加计算属性
}
val u = User(1, "Li")
val u2 = u.copy(name = "Wang")               // 复制改字段
val (id, name) = u                           // 解构
u == User(1, "Li")                           // true(自动 equals)
// 带默认值
data class Cfg(val port: Int = 8080, val debug: Boolean = false)

注意事项

  • data class 的 equals 只比较主构造器参数,不在构造器里的属性不参与
  • copy 是浅拷贝——嵌套对象仍共享引用
  • 别在 data class 里放可变集合(equals 行为会随内容变而变化,破坏 set/map)
  • 需要无参构造时给全部参数默认值

3. 密封类与 when 穷尽

描述

sealed 限定子类只能在同一模块/文件集内声明;when 据此做穷尽检查(无需 else)。是建模状态机的最佳工具。

语法和示例

sealed interface UiState {
    object Loading : UiState
    data class Ok(val items: List<Item>) : UiState
    data class Err(val msg: String, val cause: Throwable? = null) : UiState
}
fun render(s: UiState) = when (s) {
    is UiState.Loading -> Spinner()
    is UiState.Ok -> ListUi(s.items)
    is UiState.Err -> ErrorUi(s.msg)
}                                             // 无需 else,编译器保证穷尽
// when 也可作表达式赋值
val text = when {                             // 无参 when
    x > 0 -> "正"
    x == 0 -> "零"
    else -> "负"
}

注意事项

  • sealed 子类必须在同一模块内(跨模块需 move 定义)
  • 加了新变体后,所有 when 处会编译报错——这是特性不是 bug(强制处理)
  • when 有 else 时不再检查穷尽性——建模状态时别加 else
  • when 无参形式按条件判断,从上到下第一个成立分支生效

4. remember / rememberSaveable

描述

remember 在重组间保持值(配置变更/进程重建会丢);rememberSaveable 存 Bundle(旋转屏幕存活,需可序列化类型)。

语法和示例

var count by remember { mutableStateOf(0) }              // 重组保持
var draft by rememberSaveable { mutableStateOf("") }     // 配置变更存活
// 带 key:key 变则重新计算
val filtered = remember(query) { list.filter { it.matches(query) } }
// 自定义 Saver
var tab by rememberSaveable(stateSaver = ...) { mutableStateOf(0) }

注意事项

  • remember 存的是 Composition 生命周期,Activity 重建(旋转)就丢——用户输入必须 Saveable
  • remember 的 lambda 只在首次组合时执行,之后直接用缓存
  • Composition 内不要做副作用(网络/IO)——用 LaunchedEffect
  • remember(key) 的 key 变化会丢弃旧值重新计算,注意性能

5. 状态提升与单向数据流

描述

把状态提到共同父级,子组件无状态只接收值+回调。这样状态可被多处共享、可测试、可复用。

语法和示例

// 无状态展示
@Composable
fun CounterText(count: Int, modifier: Modifier = Modifier) {
    Text("$count", modifier)
}
// 有状态容器
@Composable
fun CounterHost() {
    var n by rememberSaveable { mutableStateOf(0) }
    CounterText(n)                       // 值向下
    Button(onClick = { n++ }) { Text("+1") }   // 事件向上
}

注意事项

  • 状态提升到最合适的共同祖先——提太高会让无关子树跟着重组
  • 子组件不要同时接收 state 和 setState 两个参数名,用「值 + onX 回调」约定
  • ViewModel 承载业务状态(比 Composable 内 remember 更适合跨配置变更)
  • UI 状态(展开/选中)可留在 Composable;业务状态放 ViewModel

6. 重组稳定性与性能

描述

参数 equals 相等则跳过重组。不稳定类型(可变 List、未标 @Immutable 的类)会使跳过失效。

语法和示例

// 稳定:全 val 的 data class
@Immutable
data class Item(val id: Long, val title: String)
// 不稳定:MutableList 参数
@Composable fun BadList(items: MutableList<String>) {}   // 每次都重组
// 改法:传不可变集合或 @Immutable 包装
@Composable fun GoodList(items: List<String>) {}
// 列表 key 必给
LazyColumn { items(items, key = { it.id }) { } }

注意事项

  • @Stable 承诺「公开属性变化会通知 Composition」,@Immutable 承诺「永不变」——用错会静默丢刷新
  • Lambda 参数也会影响稳定性:用方法引用或 remember 的 lambda
  • derivedStateOf 从高频状态派生低频结果,减少重组范围
  • Layout Inspector 可看重组/跳过次数,是首要排查工具

7. Modifier 顺序语义

描述

Modifier 链是有序的,顺序决定嵌套关系(padding 在内还是外、背景含不含边距)。

语法和示例

Text("A", Modifier.padding(16.dp).background(Color.Yellow))
// 顺序:先 padding 再 background → 黄底包含内边距
Text("B", Modifier.background(Color.Yellow).padding(16.dp))
// 顺序相反 → 黄底被内边距推开(视觉更小)
// 常用
Modifier.fillMaxWidth().height(48.dp)
    .clip(RoundedCornerShape(8.dp))
    .clickable { onClick() }
Row { Text("a", Modifier.weight(1f)); Text("b", Modifier.weight(2f)) }

注意事项

  • weight 只在 Row/Column 作用域内可用
  • clickable 放最后会把 ripple 画在最上层;放前面则可能被裁剪
  • size 与 fillMaxWidth 冲突时先声明的生效(顺序敏感)
  • 自定义 Modifier 用 composed 或 Modifier.Node(新 API)避免性能坑

8. LazyColumn – 虚拟化列表

描述

只组合可见项;items/item 混排;稳定 key 保动画与滚动位置。

语法和示例

LazyColumn(contentPadding = PaddingValues(16.dp)) {
    item { Header() }
    items(articles, key = { it.id }) { a ->
        ArticleRow(a, Modifier.animateItemPlacement())
    }
    item { if (hasMore) Loader() }
}
// 分组与粘性头
LazyColumn { stickyHeader { Text("A") }; items(...) }
// 状态提升滚动位置
val state = rememberLazyListState()
LazyColumn(state = state) { }

注意事项

  • key 缺失时增删会导致所有项重建(动画错乱、状态丢失)
  • 别在 item 里做重计算——用 remember 提前算好
  • LazyColumn 不能嵌套同方向可滚动容器(会崩或布局异常)
  • 内容高度不固定时避免依赖 index 的滚动恢复

9. 结构化并发作用域

描述

lifecycleScope/viewModelScope 随宿主生命周期自动取消;coroutineScope/withContext 建子作用域;async 并行。

语法和示例

viewModelScope.launch {                          // 页面销毁自动取消
    val data = withContext(Dispatchers.IO) { api.fetch() }
    _state.value = UiState.Ok(data)
}
// 并行
coroutineScope {
    val a = async { loadA() }
    val b = async { loadB() }
    a.await() + b.await()
}
// 超时
withTimeout(5000) { fetch() }

注意事项

  • GlobalScope 不要用——任务无法取消且泄漏
  • launch 用于「不关心结果」,async 用于「需要返回值」
  • withContext 切线程池但不新起协程(同一协程换 Dispatcher)
  • 取消是协作式的:循环里要检查 isActive 或调 suspend 函数

10. Flow 与 StateFlow

描述

Flow 是冷流(collect 才启动);StateFlow 持最新状态(有初值、可重复读);SharedFlow 发事件(无初值)。

语法和示例

flow { emit(1); emit(2) }
    .map { it * 2 }
    .filter { it > 2 }
    .catch { e -> emit(0) }
    .collect { v -> use(v) }

private val _state = MutableStateFlow<UiState>(UiState.Loading)
val state: StateFlow<UiState> = _state.asStateFlow()

// UI 收集(生命周期安全)
val ui by viewModel.state.collectAsStateWithLifecycle()

// 搜索防抖
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
val results = query.debounce(300).flatMapLatest { repo.search(it) }
    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())

注意事项

  • StateFlow 会去重(相同值不重复发射);要每次事件都收到用 SharedFlow
  • collectAsState 在后台仍收集,浪费资源——必须用 collectAsStateWithLifecycle
  • stateIn 的 WhileSubscribed(5000) 让无人订阅 5 秒后停止上游,避免浪费
  • flatMapLatest 会取消上一个内层流——搜索场景正确;并发场景用 flatMapMerge

11. Navigation Compose

描述

路由字符串 + 参数声明;NavHost 承载,composable 声明目的地。

语法和示例

NavHost(nav, startDestination = "home") {
    composable("home") {
        Home(onOpen = { nav.navigate("article/$it") })
    }
    composable(
        "article/{id}",
        arguments = listOf(navArgument("id") { type = NavType.LongType })
    ) { entry ->
        val id = entry.arguments?.getLong("id") ?: return@composable
        Article(id)
    }
}
// 返回结果
nav.previousBackStackEntry?.savedStateHandle?.set("key", value)
nav.popBackStack()

注意事项

  • 参数类型必须在 arguments 里声明,否则 getString 拿到的是字符串
  • navigation-compose 2.10+ 建议用类型安全路由(@Serializable 数据类)替代字符串
  • 回退栈操作注意 lifecycle:popBackStack 后不要再引用旧 entry
  • 深链需在 NavHost 外配置 deepLinks 参数
上一篇
下一篇