Skip to Content
Kotlin📘 Ngôn ngữ KotlinInline Classes / Value Classes

Inline Classes (Value Classes) trong Kotlin

1. Giới thiệu

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.

2. Cú pháp cơ bản

@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! }

Tại sao dùng Value Classes?

// ❌ Không có type safety fun sendEmail(to: String, subject: String, body: String) { } // Dễ nhầm thứ tự! sendEmail("Hello", "alice@example.com", "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("alice@example.com"), Subject("Hello"), Body("Message"))

3. Các quy tắc

@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() }

4. Methods và Properties

@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 }

5. Implementing Interfaces

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.

6. Use Cases thực tế

Domain Primitives

@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 }

Type-safe IDs

@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 }

Units of Measurement

@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") }

Duration (before Kotlin Duration)

@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 }

7. Boxing và Performance

@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 }

8. So sánh với Type Alias

// 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 }

📝 Tóm tắt

FeatureDescription
Syntax@JvmInline value class Name(val value: Type)
PropertiesOnly 1 primary property, can have computed properties
MethodsCan have methods và implement interfaces
PerformanceInlined at runtime (no boxing in most cases)
Type safetyPrevents 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)
Last updated on