@Immutable
是Jetpack Compose中的一个重要注解,它比 @Stable
更严格,专门用于标记不可变类型。
@Immutable的定义
概念
@Immutable
表示被标记的类型是完全不可变的,即一旦创建就永远不会改变。
kotlin
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class Immutable
不可变性的严格要求
要被标记为 @Immutable
,类型必须满足:
- 所有公共属性都是不可变的:只读属性,且引用的对象也不可变
- 所有公共方法都是纯函数:相同输入总是产生相同输出,无副作用
- 类实例一旦创建就永不改变:没有任何可变状态
@Immutable vs @Stable 对比
kotlin
// @Stable:可以变化,但变化时会通知Compose
@Stable
class StableCounter {
private var _value by mutableStateOf(0)
val value: Int get() = _value
fun increment() { _value++ } // 可以改变,但会通知
}
// @Immutable:完全不可变
@Immutable
data class ImmutablePoint(
val x: Int,
val y: Int
) {
// 不能有任何可变状态
// 所有操作都返回新实例
fun move(dx: Int, dy: Int) = ImmutablePoint(x + dx, y + dy)
}
@Immutable解决的核心问题
问题1:深度比较的性能开销
kotlin
// ❌ 没有@Immutable:需要深度比较
data class ComplexData(
val items: List<Item>,
val metadata: Map<String, Any>,
val config: Configuration
)
@Composable
fun ExpensiveComponent(data: ComplexData) {
// Compose需要深度比较所有字段来判断是否重组
// 对于复杂对象,这可能很昂贵
Text("Items count: ${data.items.size}")
}
// ✅ 使用@Immutable:可以优化比较
@Immutable
data class ImmutableComplexData(
val items: List<Item>,
val metadata: Map<String, Any>,
val config: Configuration
)
@Composable
fun OptimizedComponent(data: ImmutableComplexData) {
// Compose知道这是不可变的,可以使用更高效的比较策略
Text("Items count: ${data.items.size}")
}
问题2:引用相等性的不确定性
kotlin
// ❌ 问题场景:即使内容相同,引用不同导致重组
@Composable
fun ProblematicParent() {
var count by remember { mutableStateOf(0) }
// 每次重组都创建新的对象,即使内容相同
val config = UserConfig(
theme = "dark",
language = "zh",
version = 1
)
ChildComponent(config = config) // 总是重组
Button(onClick = { count++ }) {
Text("Trigger recomposition")
}
}
// ✅ 解决方案:使用@Immutable
@Immutable
data class UserConfig(
val theme: String,
val language: String,
val version: Int
)
@Composable
fun OptimizedParent() {
var count by remember { mutableStateOf(0) }
// 使用remember缓存不可变对象
val config = remember {
UserConfig(
theme = "dark",
language = "zh",
version = 1
)
}
ChildComponent(config = config) // 不会重组
Button(onClick = { count++ }) {
Text("Trigger recomposition")
}
}
使用场景详解
场景1:配置类和常量类
kotlin
@Immutable
data class AppConfig(
val apiBaseUrl: String,
val timeoutMs: Long,
val retryCount: Int,
val features: Set<String>
) {
companion object {
val DEFAULT = AppConfig(
apiBaseUrl = "https://api.example.com",
timeoutMs = 30_000L,
retryCount = 3,
features = setOf("feature1", "feature2")
)
}
}
@Immutable
data class ThemeColors(
val primary: Color,
val secondary: Color,
val background: Color,
val surface: Color
) {
companion object {
val Light = ThemeColors(
primary = Color(0xFF6200EE),
secondary = Color(0xFF03DAC6),
background = Color(0xFFFFFFFF),
surface = Color(0xFFFFFFFF)
)
val Dark = ThemeColors(
primary = Color(0xFFBB86FC),
secondary = Color(0xFF03DAC6),
background = Color(0xFF121212),
surface = Color(0xFF121212)
)
}
}
场景2:数据传输对象(DTO)
kotlin
@Immutable
data class UserProfile(
val id: String,
val name: String,
val email: String,
val avatar: String,
val preferences: UserPreferences
)
@Immutable
data class UserPreferences(
val notifications: Boolean,
val darkMode: Boolean,
val language: String
)
@Immutable
data class ApiResponse<T>(
val data: T?,
val message: String,
val status: Int,
val timestamp: Long
)
// 使用示例
@Composable
fun UserProfileCard(profile: UserProfile) {
// 由于profile是不可变的,Compose可以进行高效比较
Card {
Column {
AsyncImage(
model = profile.avatar,
contentDescription = null
)
Text(profile.name)
Text(profile.email)
}
}
}
场景3:几何和数学类型
kotlin
@Immutable
data class Point(val x: Float, val y: Float) {
operator fun plus(other: Point) = Point(x + other.x, y + other.y)
operator fun minus(other: Point) = Point(x - other.x, y - other.y)
operator fun times(scalar: Float) = Point(x * scalar, y * scalar)
}
@Immutable
data class Rectangle(
val topLeft: Point,
val size: Size
) {
val bottomRight: Point get() = Point(
topLeft.x + size.width,
topLeft.y + size.height
)
fun contains(point: Point): Boolean {
return point.x >= topLeft.x &&
point.x <= bottomRight.x &&
point.y >= topLeft.y &&
point.y <= bottomRight.y
}
}
@Immutable
data class Size(val width: Float, val height: Float) {
val area: Float get() = width * height
val aspectRatio: Float get() = width / height
}
场景4:UI状态快照
kotlin
@Immutable
data class LoadingState<T>(
val isLoading: Boolean = false,
val data: T? = null,
val error: String? = null,
val lastUpdated: Long = 0L
) {
val isSuccess: Boolean get() = data != null && !isLoading && error == null
val isError: Boolean get() = error != null && !isLoading
val isEmpty: Boolean get() = data == null && !isLoading && error == null
companion object {
fun <T> loading() = LoadingState<T>(isLoading = true)
fun <T> success(data: T) = LoadingState(
data = data,
lastUpdated = System.currentTimeMillis()
)
fun <T> error(message: String) = LoadingState<T>(error = message)
}
}
@Immutable
data class PagingState<T>(
val items: List<T> = emptyList(),
val isLoading: Boolean = false,
val hasMore: Boolean = true,
val error: String? = null
) {
fun loadMore() = copy(isLoading = true, error = null)
fun loadSuccess(newItems: List<T>, hasMore: Boolean) = copy(
items = items + newItems,
isLoading = false,
hasMore = hasMore,
error = null
)
fun loadError(error: String) = copy(
isLoading = false,
error = error
)
}
场景5:函数式数据结构
kotlin
@Immutable
sealed class ImmutableList<out T> {
abstract val size: Int
abstract fun get(index: Int): T
abstract fun add(element: @UnsafeVariance T): ImmutableList<T>
object Empty : ImmutableList<Nothing>() {
override val size = 0
override fun get(index: Int) = throw IndexOutOfBoundsException()
override fun add(element: Nothing) = Node(element, this)
}
data class Node<T>(
val head: T,
val tail: ImmutableList<T>
) : ImmutableList<T>() {
override val size = 1 + tail.size
override fun get(index: Int): T = when {
index == 0 -> head
index > 0 -> tail.get(index - 1)
else -> throw IndexOutOfBoundsException()
}
override fun add(element: T) = Node(element, this)
}
}
// 使用示例
@Composable
fun ImmutableListDisplay(list: ImmutableList<String>) {
LazyColumn {
items(list.size) { index ->
Text(list.get(index))
}
}
}
带来的优化效果
1. 编译时优化
kotlin
// Compose编译器对@Immutable的优化
@Immutable
data class Product(val id: String, val name: String, val price: Double)
@Composable
fun ProductCard(product: Product) {
// 编译器生成的伪代码:
// if (product !== previousProduct) {
// // 可以直接使用引用比较,无需深度比较
// recompose()
// }
Card {
Column {
Text(product.name)
Text("$${product.price}")
}
}
}
2. 内存分配优化
kotlin
@Immutable
data class CacheKey(
val userId: String,
val resourceType: String,
val version: Int
)
class OptimizedCache {
private val cache = mutableMapOf<CacheKey, Any>()
fun get(key: CacheKey): Any? {
// 由于CacheKey是不可变的,可以安全地用作Map的key
// 不用担心key被修改导致的问题
return cache[key]
}
fun put(key: CacheKey, value: Any) {
cache[key] = value
}
}
3. 并发安全性
kotlin
@Immutable
data class ThreadSafeConfig(
val settings: Map<String, String>,
val flags: Set<String>,
val values: List<Int>
) {
// 不可变对象天然线程安全
// 可以在多个线程间安全共享
}
class ConfigManager {
private var _config by mutableStateOf(ThreadSafeConfig(
settings = emptyMap(),
flags = emptySet(),
values = emptyList()
))
val config: ThreadSafeConfig get() = _config
fun updateConfig(newConfig: ThreadSafeConfig) {
_config = newConfig // 原子操作,线程安全
}
}
4. 性能测试对比
kotlin
@Composable
fun PerformanceComparison() {
// 测量重组性能
val mutableData = remember { MutableData() }
val immutableData = remember { ImmutableData() }
Column {
// 可变数据:每次都需要深度比较
MutableDataComponent(mutableData)
// 不可变数据:快速引用比较
ImmutableDataComponent(immutableData)
}
}
data class MutableData(
var items: MutableList<String> = mutableListOf(),
var count: Int = 0
)
@Immutable
data class ImmutableData(
val items: List<String> = emptyList(),
val count: Int = 0
)
@Composable
fun MutableDataComponent(data: MutableData) {
// Compose必须检查data的所有字段是否改变
// 对于大型对象,这会很昂贵
Text("Mutable: ${data.items.size}")
}
@Composable
fun ImmutableDataComponent(data: ImmutableData) {
// Compose可以使用快速的引用比较
Text("Immutable: ${data.items.size}")
}
为什么@Immutable能解决这些问题
1. 编译器信任机制
kotlin
// 编译器对@Immutable的处理
@Immutable
class TrustedImmutable(val value: String)
@Composable
fun ComponentWithTrustedParam(param: TrustedImmutable) {
// 编译器生成的比较逻辑:
// if (param !== previousParam) {
// // 只需要引用比较,因为@Immutable保证了不变性
// recompose()
// } else {
// skip() // 跳过重组
// }
}
2. 引用透明性
kotlin
@Immutable
data class MathVector(val x: Double, val y: Double, val z: Double) {
// 纯函数:相同输入总是相同输出
fun magnitude(): Double = sqrt(x * x + y * y + z * z)
fun normalize(): MathVector {
val mag = magnitude()
return MathVector(x / mag, y / mag, z / mag)
}
// 可以安全缓存计算结果
val cachedMagnitude by lazy { magnitude() }
}
@Composable
fun VectorDisplay(vector: MathVector) {
// 由于vector是不可变的,Compose可以安全地假设:
// 1. vector.magnitude()总是返回相同值
// 2. 可以缓存计算结果
// 3. 引用相等 == 值相等
Text("Vector: (${vector.x}, ${vector.y}, ${vector.z})")
Text("Magnitude: ${vector.magnitude()}")
}
3. 结构共享优化
kotlin
@Immutable
data class ImmutableTree<T>(
val value: T,
val left: ImmutableTree<T>? = null,
val right: ImmutableTree<T>? = null
) {
// 添加节点返回新树,但共享未修改的部分
fun insert(newValue: T): ImmutableTree<T> = when {
newValue < value -> copy(left = left?.insert(newValue) ?: ImmutableTree(newValue))
newValue > value -> copy(right = right?.insert(newValue) ?: ImmutableTree(newValue))
else -> this // 值相同,返回自身
}
}
@Composable
fun TreeVisualizer(tree: ImmutableTree<Int>) {
// 由于使用结构共享,大部分节点在修改后仍然是相同的引用
// Compose可以跳过渲染未改变的子树
TreeNode(tree)
}
最佳实践指南
1. 何时使用@Immutable
kotlin
// ✅ 适合使用@Immutable的场景
// 值对象
@Immutable
data class Money(val amount: BigDecimal, val currency: String) {
operator fun plus(other: Money): Money {
require(currency == other.currency)
return Money(amount + other.amount, currency)
}
}
// 配置对象
@Immutable
data class DatabaseConfig(
val host: String,
val port: Int,
val database: String,
val ssl: Boolean
)
// 枚举状态
@Immutable
sealed class NetworkState {
object Loading : NetworkState()
object Connected : NetworkState()
data class Error(val message: String) : NetworkState()
object Disconnected : NetworkState()
}
2. 何时不使用@Immutable
kotlin
// ❌ 不适合使用@Immutable的场景
// 包含可变状态的类
class Repository {
private var cache: MutableMap<String, Any> = mutableMapOf()
fun getData(key: String): Any? = cache[key]
fun putData(key: String, value: Any) { cache[key] = value }
}
// 有副作用的类
class Logger {
fun log(message: String) {
println(message) // 副作用:输出到控制台
}
}
// 依赖外部可变状态的类
class TimeBasedCalculator {
fun getCurrentValue(): Long = System.currentTimeMillis() // 每次调用结果不同
}
3. 组合使用技巧
kotlin
// 创建不可变的复合对象
@Immutable
data class GameState(
val player: Player,
val enemies: List<Enemy>,
val score: Int,
val level: Int
) {
fun playerMove(direction: Direction): GameState = copy(
player = player.move(direction)
)
fun addEnemy(enemy: Enemy): GameState = copy(
enemies = enemies + enemy
)
fun updateScore(points: Int): GameState = copy(
score = score + points
)
}
@Immutable
data class Player(val x: Int, val y: Int, val health: Int) {
fun move(direction: Direction): Player = when (direction) {
Direction.UP -> copy(y = y - 1)
Direction.DOWN -> copy(y = y + 1)
Direction.LEFT -> copy(x = x - 1)
Direction.RIGHT -> copy(x = x + 1)
}
}
@Immutable
data class Enemy(val x: Int, val y: Int, val type: EnemyType)
enum class Direction { UP, DOWN, LEFT, RIGHT }
enum class EnemyType { GOBLIN, ORC, DRAGON }
4. 与Compose状态集成
kotlin
@Composable
fun GameScreen() {
var gameState by remember {
mutableStateOf(
GameState(
player = Player(0, 0, 100),
enemies = emptyList(),
score = 0,
level = 1
)
)
}
// 由于GameState是不可变的,状态更新总是创建新实例
// Compose可以高效地检测变化
GameRenderer(
state = gameState,
onPlayerMove = { direction ->
gameState = gameState.playerMove(direction)
},
onScoreUpdate = { points ->
gameState = gameState.updateScore(points)
}
)
}
@Composable
fun GameRenderer(
state: GameState,
onPlayerMove: (Direction) -> Unit,
onScoreUpdate: (Int) -> Unit
) {
// 由于state是不可变的,只有真正改变时才重组
Column {
Text("Score: ${state.score}")
Text("Level: ${state.level}")
GameBoard(
player = state.player,
enemies = state.enemies
)
ControlPanel(onMove = onPlayerMove)
}
}
性能监控和调试
1. 重组计数器
kotlin
@Composable
fun ImmutablePerformanceTest() {
var updateTrigger by remember { mutableStateOf(0) }
// 不可变数据
val immutableData = remember(updateTrigger) {
ImmutableComplexData.create(updateTrigger)
}
Column {
RecompositionCounter("Immutable") {
ImmutableDataDisplay(immutableData)
}
Button(onClick = { updateTrigger++ }) {
Text("Update Data")
}
}
}
@Composable
fun RecompositionCounter(
label: String,
content: @Composable () -> Unit
) {
val recomposeCount = remember { mutableIntStateOf(0) }
SideEffect {
recomposeCount.intValue++
}
Column {
Text("$label - 重组次数: ${recomposeCount.intValue}")
content()
}
}
2. 内存使用监控
kotlin
@Immutable
data class LargeImmutableObject(
val data: ByteArray,
val metadata: Map<String, String>
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is LargeImmutableObject) return false
return data.contentEquals(other.data) && metadata == other.metadata
}
override fun hashCode(): Int {
return 31 * data.contentHashCode() + metadata.hashCode()
}
companion object {
fun create(size: Int): LargeImmutableObject {
val startMemory = Runtime.getRuntime().let {
it.totalMemory() - it.freeMemory()
}
val obj = LargeImmutableObject(
data = ByteArray(size),
metadata = mapOf("size" to size.toString())
)
val endMemory = Runtime.getRuntime().let {
it.totalMemory() - it.freeMemory()
}
Log.d("Memory", "创建对象使用内存: ${endMemory - startMemory} bytes")
return obj
}
}
}
总结
@Immutable的核心价值
- 性能优化:启用引用相等性比较,避免深度比较
- 线程安全:不可变对象天然线程安全
- 可预测性:纯函数特性,无副作用
- 缓存友好:可以安全地缓存不可变对象
- 结构共享:支持高效的不可变数据结构
解决问题的根本原理
- 编译器信任:告诉Compose可以使用更高效的比较策略
- 引用透明:相同引用意味着相同内容
- 不变性保证:一旦创建就永不改变,消除了状态同步问题
选择建议
- 值对象和数据类:优先使用@Immutable
- 配置和常量:总是使用@Immutable
- 需要可变状态:使用@Stable
- 复杂业务逻辑:根据具体情况选择
记住 :@Immutable
是一个强契约,只有在真正不可变的类型上使用,错误使用可能导致意外行为!