Bỏ qua để đến nội dung

📦 Inline/Value Classes

Inline classes (từ Kotlin 1.5+ gọi là value classes) cho phép tạo type-safe wrappers mà không có overhead runtime. Compiler sẽ inline giá trị bên trong, loại bỏ wrapper object.

@JvmInline
value class Password(val value: String)
@JvmInline
value class Username(val value: String)
fun authenticate(username: Username, password: Password) {
println("Authenticating ${username.value}...")
}
fun main() {
val username = Username("alice")
val password = Password("secret123")
authenticate(username, password) // OK
// authenticate(password, username) // Compile error!
}
// ❌ Không có type safety
fun sendEmail(to: String, subject: String, body: String) { }
// Dễ nhầm thứ tự!
sendEmail("Hello", "[email protected]", "Message") // Bug!
// ✅ Với value classes
@JvmInline value class Email(val value: String)
@JvmInline value class Subject(val value: String)
@JvmInline value class Body(val value: String)
fun sendEmail(to: Email, subject: Subject, body: Body) { }
// Compiler catches errors
sendEmail(Email("[email protected]"), Subject("Hello"), Body("Message"))
@JvmInline
value class UserId(val id: Long) // ✅ OK
// ❌ Phải có exactly 1 property trong primary constructor
// value class Invalid(val a: Int, val b: Int)
// ❌ Không thể có init blocks với side effects
// value class Invalid(val value: Int) {
// init { println("Created") } // Not allowed
// }
// ✅ Có thể có secondary properties (computed)
@JvmInline
value class Name(val value: String) {
val length: Int get() = value.length
val uppercase: String get() = value.uppercase()
}
@JvmInline
value class Percentage(val value: Double) {
// Computed properties
val asDecimal: Double get() = value / 100
// Functions
fun format(): String = "$value%"
// Validation trong init
init {
require(value in 0.0..100.0) { "Percentage must be between 0 and 100" }
}
}
fun main() {
val discount = Percentage(15.0)
println(discount.format()) // 15.0%
println(discount.asDecimal) // 0.15
// Percentage(150.0) // Throws IllegalArgumentException
}
interface Printable {
fun print()
}
@JvmInline
value class Message(val text: String) : Printable {
override fun print() {
println("Message: $text")
}
}
fun main() {
val msg = Message("Hello")
msg.print() // Message: Hello
val printable: Printable = msg
printable.print() // Message: Hello
}

Note: Khi dùng interface, boxing có thể xảy ra.

@JvmInline
value class CustomerId(val value: Long) {
init {
require(value > 0) { "Customer ID must be positive" }
}
}
@JvmInline
value class OrderId(val value: Long)
@JvmInline
value class Money(val cents: Long) {
val dollars: Double get() = cents / 100.0
operator fun plus(other: Money) = Money(cents + other.cents)
operator fun minus(other: Money) = Money(cents - other.cents)
operator fun times(multiplier: Int) = Money(cents * multiplier)
fun format(): String = "$${"%.2f".format(dollars)}"
}
fun processOrder(customerId: CustomerId, orderId: OrderId, amount: Money) {
println("Processing order $orderId for customer $customerId: ${amount.format()}")
}
fun main() {
val customerId = CustomerId(12345)
val orderId = OrderId(67890)
val amount = Money(9999) // $99.99
processOrder(customerId, orderId, amount)
// processOrder(orderId, customerId, amount) // Compile error!
val total = amount + Money(500) // $99.99 + $5.00
println(total.format()) // $104.99
}
@JvmInline
value class UserId(val value: String)
@JvmInline
value class PostId(val value: String)
@JvmInline
value class CommentId(val value: String)
class PostRepository {
fun getPost(id: PostId): Post? = TODO()
fun getComments(postId: PostId): List<Comment> = TODO()
}
class UserRepository {
fun getUser(id: UserId): User? = TODO()
}
// Compiler prevents mixing up IDs
fun loadPostWithAuthor(postId: PostId, userId: UserId) {
val repo = PostRepository()
// repo.getPost(userId) // Compile error! Type mismatch
val post = repo.getPost(postId) // OK
}
@JvmInline
value class Meters(val value: Double) {
fun toKilometers() = Kilometers(value / 1000)
fun toFeet() = Feet(value * 3.28084)
}
@JvmInline
value class Kilometers(val value: Double) {
fun toMeters() = Meters(value * 1000)
}
@JvmInline
value class Feet(val value: Double) {
fun toMeters() = Meters(value / 3.28084)
}
// Extension functions for nice syntax
val Int.meters get() = Meters(this.toDouble())
val Int.km get() = Kilometers(this.toDouble())
val Double.meters get() = Meters(this)
fun main() {
val distance = 5.km
println("${distance.value} km = ${distance.toMeters().value} meters")
val height = 100.meters
println("${height.value} m = ${height.toFeet().value} feet")
}
@JvmInline
value class Milliseconds(val value: Long) {
val seconds: Long get() = value / 1000
val minutes: Long get() = value / 60_000
operator fun plus(other: Milliseconds) = Milliseconds(value + other.value)
operator fun compareTo(other: Milliseconds) = value.compareTo(other.value)
}
val Int.ms get() = Milliseconds(this.toLong())
val Int.seconds get() = Milliseconds(this * 1000L)
val Int.minutes get() = Milliseconds(this * 60_000L)
fun main() {
val timeout = 30.seconds
val delay = 500.ms
println("${timeout.value} ms") // 30000 ms
println("${(timeout + delay).seconds} seconds") // 30 seconds
}
@JvmInline
value class Id(val value: Long)
fun directUse(id: Id) {
// No boxing - id được inline thành Long
println(id.value)
}
fun nullableUse(id: Id?) {
// Boxing occurs - cần object để represent null
println(id?.value)
}
fun genericUse(item: Any) {
// Boxing occurs - cần object cho Any
println(item)
}
fun main() {
val id = Id(123)
directUse(id) // No boxing
nullableUse(id) // Boxing
genericUse(id) // Boxing
}
// Type alias - chỉ là tên khác, KHÔNG type-safe
typealias UserId = Long
typealias OrderId = Long
fun processWithAlias(userId: UserId, orderId: OrderId) {}
// Value class - type-safe, có runtime cost minimal
@JvmInline value class SafeUserId(val value: Long)
@JvmInline value class SafeOrderId(val value: Long)
fun processWithValue(userId: SafeUserId, orderId: SafeOrderId) {}
fun main() {
val userId: UserId = 1L
val orderId: OrderId = 2L
// ❌ Type alias không ngăn lỗi
processWithAlias(orderId, userId) // Compiles! Bug!
val safeUserId = SafeUserId(1L)
val safeOrderId = SafeOrderId(2L)
// ✅ Value class catches errors
// processWithValue(safeOrderId, safeUserId) // Compile error!
processWithValue(safeUserId, safeOrderId) // OK
}

Feature Description
Syntax @JvmInline value class Name(val value: Type)
Properties Only 1 primary property, can have computed properties
Methods Can have methods và implement interfaces
Performance Inlined at runtime (no boxing in most cases)
Type safety Prevents mixing up similar types

Khi nào dùng value classes:

  • Wrapper primitives với meaning khác nhau (IDs, units)
  • Domain primitives với validation
  • Type-safe API design
  • Khi cần type safety mà không muốn overhead

Khi KHÔNG nên dùng:

  • Khi cần multiple properties
  • Khi cần inheritance
  • Khi thường xuyên dùng với generics/nullables (boxing)