by 是 Kotlin 中的一个关键字 ,表示委托(delegation)。它的含义是:"将这个属性的 getter 和 setter 委托给另一个对象处理"。
1. 基本概念
kotlin
// 没有 by:直接赋值
val a = "Hello" // a 存储字符串本身
// 有 by:委托
val b by delegateObject // b 的读写操作委托给 delegateObject 处理
2. by 在 Compose 中的具体含义
在 Compose 中,by 通常与 mutableStateOf 一起使用:
kotlin
// 没有 by:myText 是一个 MutableState<String> 对象
val myText = remember { mutableStateOf("Hello") }
// 使用:myText.value = "World" 需要 .value
// 有 by:myText 看起来是一个普通的 String,但实际上委托给了 MutableState
var myText by remember { mutableStateOf("Hello") }
// 使用:myText = "World" 不需要 .value,看起来像普通变量
3. 底层原理:属性委托
Kotlin 的委托机制:
kotlin
// 自定义委托类
class SimpleDelegate<T>(private var value: T) {
// getValue 方法:当读取属性时调用
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
println("读取属性: ${property.name}")
return value
}
// setValue 方法:当设置属性时调用(仅 var)
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) {
println("设置属性: ${property.name} = $newValue")
value = newValue
}
}
// 使用委托
var message by SimpleDelegate("Hello")
// 实际执行:message = SimpleDelegate("Hello")
println(message) // 调用 getValue(),输出: Hello
message = "World" // 调用 setValue("World")
4. Compose 中 mutableStateOf 的委托实现
mutableStateOf 返回的对象实现了 State 接口,它提供了 getValue 和 setValue 操作符:
kotlin
// 简化的 State 接口
interface State<T> {
var value: T
operator fun getValue(thisRef: Any?, property: KProperty<*>): T
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T)
}
// 实际使用
var count by mutableStateOf(0)
// 相当于:
// val countState = mutableStateOf(0)
// var count: Int
// get() = countState.getValue(this, ::count)
// set(value) = countState.setValue(this, ::count, value)
5. 为什么需要 by?
没有 by 的缺点:
kotlin
// 方式1:不使用委托
val countState = remember { mutableStateOf(0) }
// 使用繁琐:
countState.value++ // 需要 .value
if (countState.value > 5) // 需要 .value
countState.value = 10 // 需要 .value
有 by 的优点:
kotlin
// 方式2:使用委托
var count by remember { mutableStateOf(0) }
// 使用简洁:
count++ // 看起来像普通变量
if (count > 5) // 看起来像普通变量
count = 10 // 看起来像普通变量
6. by 的其他使用场景
除了 Compose,by 还有多种用途:
kotlin
// 1. 惰性初始化(lazy)
val heavyObject by lazy {
println("首次访问时初始化")
HeavyObject()
}
// 2. 观察者模式(observable)
var name by Delegates.observable("") { prop, old, new ->
println("$old -> $new")
}
// 3. 映射委托(map)
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
val user = User(mapOf("name" to "Alice", "age" to 25))
println(user.name) // "Alice"
7. 在 Compose 中的完整示例
kotlin
@Composable
fun Counter() {
// 1. 使用 by(推荐)
var count1 by remember { mutableStateOf(0) }
// 2. 不使用 by
val count2 = remember { mutableStateOf(0) }
Column {
// 使用 count1(简洁)
Button(onClick = { count1++ }) {
Text("Count1: $count1")
}
// 使用 count2(繁琐)
Button(onClick = { count2.value++ }) {
Text("Count2: ${count2.value}")
}
// 3. 只读状态也可以使用 by
val doubled by remember {
derivedStateOf { count1 * 2 }
}
Text("Doubled: $doubled")
}
}
8. 重要区别总结
| 特性 | 有 by |
没有 by |
|---|---|---|
| 类型 | 看起来是基础类型(Int, String) | 是 MutableState<T> 类型 |
| 读取 | val current = count |
val current = count.value |
| 写入 | count = 10 |
count.value = 10 |
| 用途 | UI 状态变量 | 需要传递状态对象本身 |
| 可读性 | 更高,像普通变量 | 较低,显式表明是状态 |
9. 记忆技巧
可以把 by 理解为:
- "通过...来管理":这个属性通过后面的委托对象来管理
- "由...代理":这个属性的读写操作由委托对象代理
- 语法糖:让代码更简洁的语法糖
简单规则:
- 如果属性需要在 UI 中直接使用(读取/写入),用
by - 如果属性对象本身需要作为参数传递,不用
by
10. 实际应用建议
kotlin
// ✅ 推荐:UI 状态使用 by
var text by remember { mutableStateOf("") }
var isSelected by remember { mutableStateOf(false) }
var counter by remember { mutableIntStateOf(0) }
// ✅ 推荐:需要传递的对象不用 by
val scrollState = rememberScrollState()
val pagerState = rememberPagerState()
val lazyListState = rememberLazyListState()
// 使用:
LazyColumn(state = lazyListState) { ... } // 需要传递状态对象本身
HorizontalPager(state = pagerState) { ... }
一句话总结 :by 让状态变量看起来和用起来像普通变量,但背后是委托给 MutableState 管理,这样 Compose 能自动跟踪变化并触发重组。