关于 kotlin-types-value-class
用于审查 Kotlin 值类、数据类、类型别名和基本类型选择的技能。它兼顾领域建模,并检查序列化、Java 互操作、Compose 稳定性与装箱成本。
主要功能
- 01单字段类型的选择指导
- 02检查相等性与序列化变化
- 03Java 互操作与装箱考量
- 04Compose 稳定性审查步骤
- 05重构与性能检查
适用场景
- 01审查基本类型密集的 Kotlin API
- 02评估仅含一个属性的数据类
- 03排查 Compose 稳定性报告
- 04评估 API 边界上的值类变更
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ chrisbanes/ skills --skill kotlin-types-value-class安装后在 Claude Code 中输入/kotlin-types-value-class确认已加载。
详细介绍
Kotlin value class 与 data class
核心原则
对于承载领域含义的单字段类型,优先使用 @JvmInline value class。Data class 用于聚合多个字段。
审查流程
- 查找单属性包装类型、基本类型繁多的 API,以及 UI 状态中的
@Immutable包装类型。 - 判断单个值是否代表真正的领域区别。如果不是,则保留基本类型或使用类型别名。
- 检查替换类型是否会改变相等性、序列化、Java 互操作性或热路径装箱行为。
- 只有在领域含义明确且契约变更可接受时才进行转换。
- 重新运行受影响的编译器检查和测试;对于 Compose 性能相关工作,重新检查编译器报告或重组证据。
决策流程
| 情况 | 优先选择 |
|---|---|
单字段 + 有领域含义(UserId、EmailAddress、Percentage) | @JvmInline value class |
| 单字段 + 没有领域含义(仅用于分组) | 类型别名或保留基本类型 |
| 多个字段 | Data class |
需要自定义 equals/hashCode,且行为超出包装值本身 | Data class(value class 会委托给底层类型) |
| 在已证实的热路径中用作泛型类型实参或可空类型 | Data class 或基本类型 |
// GOOD: domain-meaningful single field
@JvmInline value class UserId(val value: String)
@JvmInline value class EmailAddress(val value: String)
@JvmInline value class Percentage(val value: Float)
// BAD: data class wrapping a single domain field
data class UserId(val value: String)
// BAD: value class with no domain meaning
@JvmInline value class Wrapper(val value: String) // just use the String, or a type alias
// BAD: value class needing custom equality
@JvmInline value class CaseInsensitiveString(val value: String)
// value class equals delegates to String equals, which IS case-sensitive
// Use a data class if you need different equality semantics
Compose 稳定性流程
当 Compose 报告指出某个单字段包装类型时:
- 确认底层类型是稳定的(
String、基本类型或其他稳定类型)。 - 对于唯一作用是区分类型的包装类型,优先使用 value class,而不是添加
@Immutable。 - 不要仅为消除报告而更改公开的序列化/API 契约。
// Before: primitive value can be mixed up with other strings
data class UiState(val userId: String)
// After: domain type is stable at the Compose boundary
@JvmInline value class UserId(val value: String)
data class UiState(val userId: UserId)
重构检查
替换现有包装类型之前,检查调用方所观察到的契约:
| 检查项 | 操作 |
|---|---|
| JSON/API 格式很重要 | 验证序列化。@Serializable data class A(val value: String) 会编码为对象;value class 会编码为包装值。 |
| 需要自定义相等性或哈希 | 保留 data class。Value class 的相等性遵循包装值。 |
调用方使用 copy() 或解构 | 保留 data class,或有计划地更新调用方。Value class 不提供 data class 的便捷功能。 |
| Java 或大量使用反射的框架边界 | 验证互操作性。Java 调用方看到的是底层类型;泛型/Any 用法会装箱。 |
| 可空类型/泛型/可变参数热路径 | 转换前先测量;这些用法会装箱。 |
构造函数体、lateinit、委托属性、幕后字段 | 保留 data class 或重新设计;value class 仅存储构造函数的值。 |
有证据后再打包多个值
除非性能分析显示热路径存在分配开销,否则不要将清晰的多字段 data class 替换为位打包。如有需要,Compose 在 androidx.compose.ui.util 中提供了 packFloats、packInts 和对应的 unpack* 函数:
@JvmInline value class Offset(val packedValue: Long)
fun Offset(x: Float, y: Float): Offset = Offset(packFloats(x, y))
val Offset.x: Float get() = unpackFloat1(packedValue)
val Offset.y: Float get() = unpackFloat2(packedValue)
常见错误
| 错误 | 修正方法 |
|---|---|
| 用 data class 包装单个领域字段 | 替换为 @JvmInline value class |
| value class 没有领域含义(仅作为包装类型) | 使用类型别名或直接使用基本类型 |
| value class 需要自定义相等性 | 改用 data class |
| 热路径中将 value class 用作泛型类型实参 | 测量装箱开销;如果影响明显,则保留基本类型/data class |
对可以使用 value class 的类型添加 @Immutable 注解 | 当底层类型稳定时,替换为 value class |
忘记 @JvmInline 注解 | 单字段类始终应将 value class 与 @JvmInline 配对使用 |
审查期间的危险信号
- 只有一个属性的 data class
- 在不同值不可互换的场景中使用
String、Long或Int(例如,fun transfer(from: String, to: String, amount: Long)) - 单字段包装类型上有
@Immutable注解 - 使用类型别名来区分领域类型,而实际需要 value class 语义(类型别名会被擦除,不提供运行时保护)
不适用的情况
- 类型需要多个字段 → 使用 data class
- 类型需要自定义
equals/hashCode→ 使用 data class - 在性能关键代码中,类型经常作为可空类型或泛型使用 → 先测量自动装箱的开销
- 项目不需要区分类型安全 → 类型别名或基本类型就足够了
- 替换会悄然改变 JSON、Java、反射或框架行为
相关内容
compose-stability-diagnostics— 诊断不稳定的 Compose 参数;值类是解决方法之一
评论
支持 Markdown