You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 13:00:31