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 参数