Kotlin值类如何存储属性?自定义Money值类存格式遇问题
问题根源
值类(value class)的核心设计限制是只能有一个底层存储属性,不能额外添加带 backing field 的属性——因为值类本质是对单个值的轻量包装,编译时会尽可能内联,额外的存储字段会破坏这个特性,所以你会收到Value class cannot have properties with backing fields的错误。
你看到的kotlin.time.Duration中的storageUnit并不是带 backing field 的属性,它是基于底层存储的纳秒值推导出来的计算属性,或者是通过工厂方法创建时的逻辑关联,并没有实际存储在值类内部。
解决方案
下面提供两种可行的方式,让你在值类中保存格式信息并实现转换方法:
方案一:将格式信息编码到底层值中
通过特定分隔符把金额和格式拼接成一个字符串,值类内部通过计算属性解析出格式和金额,完全符合值类的设计要求。
@JvmInline value class Money internal constructor(val rawValue: String) { // 无backing field的计算属性,解析格式信息 val format: MoneyFormat get() { val parts = rawValue.split("_", limit = 2) return MoneyFormat.valueOf(parts[1]) } // 解析金额值的计算属性 val amount: String get() = rawValue.split("_", limit = 2)[0] // 格式转换方法 fun convertTo(newFormat: MoneyFormat): Money { // 这里替换为实际的格式转换逻辑(比如分转元、元转分) val convertedAmount = when (format) { MoneyFormat.WHOLE -> amount MoneyFormat.CENTS -> (amount.toLong() / 100).toString() MoneyFormat.DECIMAL -> (amount.toDouble() * 100).toLong().toString() } return Money("$convertedAmount_${newFormat.name}") } enum class MoneyFormat { WHOLE, // 整数金额(如元) DECIMAL, // 带小数的金额 CENTS // 分单位的金额 } companion object { // 工厂方法,确保rawValue格式合法,避免直接构造 fun of(amount: String, format: MoneyFormat): Money { require(amount.matches(Regex("\\d+"))) { "金额必须为数字" } return Money("$amount_${format.name}") } } }
方案二:用复合类型作为值类的底层属性
把金额和格式封装成Pair或自定义数据类,作为值类的唯一存储属性,再通过计算属性暴露金额和格式。这种方式可读性更强,但JVM上如果底层是Pair,可能无法完全内联(性能影响极小)。
@JvmInline value class Money internal constructor(private val data: Pair<String, MoneyFormat>) { // 计算属性暴露金额和格式 val rawValue: String get() = data.first val format: MoneyFormat get() = data.second // 格式转换方法 fun convertTo(newFormat: MoneyFormat): Money { val convertedAmount = when (format) { MoneyFormat.WHOLE -> rawValue MoneyFormat.CENTS -> (rawValue.toLong() / 100).toString() MoneyFormat.DECIMAL -> (rawValue.toDouble() * 100).toLong().toString() } return Money(convertedAmount to newFormat) } enum class MoneyFormat { WHOLE, DECIMAL, CENTS } companion object { fun of(amount: String, format: MoneyFormat): Money { require(amount.matches(Regex("\\d+"))) { "金额必须为数字" } return Money(amount to format) } } }
方案选择
- 若追求极致轻量性,优先选方案一;
- 若更看重代码可读性,选方案二。
内容的提问来源于stack exchange,提问作者testivanivan
相关产品推荐
相关产品推荐

