Careeroza
Value Classes
Type Safety Without Overhead
A @JvmInline value class wraps a single value. At compile time it is a separate type (you cannot pass a UserId where an OrderId is expected), but at runtime it is usually just the underlying value, with no extra object.
Example
@JvmInline
value class UserId(val raw: Long)
@JvmInline
value class OrderId(val raw: Long)
@JvmInline
value class Rupees(val paise: Long) {
init { require(paise >= 0) { "amount cannot be negative" } }
operator fun plus(other: Rupees) = Rupees(paise + other.paise)
override fun toString() = "Rs %d.%02d".format(paise / 100, paise % 100)
}
fun cancelOrder(user: UserId, order: OrderId) = "User ${user.raw} cancelled order ${order.raw}"
fun main() {
println(cancelOrder(UserId(7), OrderId(1001)))
// cancelOrder(OrderId(1001), UserId(7)) // compile error: types are swapped
val total = Rupees(49_900) + Rupees(4_950)
println("Total: $total")
println(runCatching { Rupees(-1) }.exceptionOrNull()?.message)
}Output
User 7 cancelled order 1001 Total: Rs 548.50 amount cannot be negative
Tip: Use value classes for IDs, money, emails and units. They catch "wrong argument order" bugs at compile time for free.