Compose @Immutable注解

@Immutable 是Jetpack Compose中的一个重要注解,它比 @Stable 更严格,专门用于标记不可变类型。

@Immutable的定义

概念

@Immutable 表示被标记的类型是完全不可变的,即一旦创建就永远不会改变。

kotlin 复制代码
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class Immutable

不可变性的严格要求

要被标记为 @Immutable,类型必须满足:

  1. 所有公共属性都是不可变的:只读属性,且引用的对象也不可变
  2. 所有公共方法都是纯函数:相同输入总是产生相同输出,无副作用
  3. 类实例一旦创建就永不改变:没有任何可变状态

@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的核心价值

  1. 性能优化:启用引用相等性比较,避免深度比较
  2. 线程安全:不可变对象天然线程安全
  3. 可预测性:纯函数特性,无副作用
  4. 缓存友好:可以安全地缓存不可变对象
  5. 结构共享:支持高效的不可变数据结构

解决问题的根本原理

  • 编译器信任:告诉Compose可以使用更高效的比较策略
  • 引用透明:相同引用意味着相同内容
  • 不变性保证:一旦创建就永不改变,消除了状态同步问题

选择建议

  • 值对象和数据类:优先使用@Immutable
  • 配置和常量:总是使用@Immutable
  • 需要可变状态:使用@Stable
  • 复杂业务逻辑:根据具体情况选择

记住@Immutable 是一个强契约,只有在真正不可变的类型上使用,错误使用可能导致意外行为!

相关推荐
橙子199110161 小时前
Kotlin 中的 Object
android·开发语言·kotlin
AD钙奶-lalala5 小时前
android:foregroundServiceType详解
android
大胃粥8 小时前
Android V app 冷启动(13) 焦点窗口更新
android
fatiaozhang952712 小时前
中兴B860AV1.1_晨星MSO9280芯片_4G和8G闪存_TTL-BIN包刷机固件包
android·linux·adb·电视盒子·av1·魔百盒刷机
fatiaozhang952713 小时前
中兴B860AV1.1_MSO9280_降级后开ADB-免刷机破解教程(非刷机)
android·adb·电视盒子·av1·魔百盒刷机·移动魔百盒·魔百盒固件
二流小码农13 小时前
鸿蒙开发:绘制服务卡片
android·ios·harmonyos
微信公众号:AI创造财富14 小时前
adb 查看android 设备的硬盘及存储空间
android·adb
码农果果14 小时前
Google 提供的一组集成测试套件----XTS
android
火柴就是我14 小时前
每日见闻之Rust中的引用规则
android