Skip to Content

Enum Class trong Kotlin

🎯 Mục tiêu: Hiểu Enum Class - tập hợp các hằng số có tên, với properties, methods, và exhaustive pattern matching.


💡 Enum là gì?

Enum (Enumeration) định nghĩa một tập hợp cố định các giá trị với tên có ý nghĩa. Thay vì dùng magic numbers hoặc strings, dùng enum để code rõ ràng và type-safe.

// ❌ Bad: Magic numbers fun getDirection(code: Int): String = when (code) { 0 -> "North" 1 -> "South" 2 -> "East" 3 -> "West" else -> "Unknown" } // ✅ Good: Enum enum class Direction { NORTH, SOUTH, EAST, WEST } fun getDirection(direction: Direction): String = when (direction) { Direction.NORTH -> "North" Direction.SOUTH -> "South" Direction.EAST -> "East" Direction.WEST -> "West" // Không cần else - compiler đảm bảo đã cover hết! }

📝 Cú pháp cơ bản

enum class Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY } fun main() { val today = Day.FRIDAY println(today) // FRIDAY println(today.name) // FRIDAY (String) println(today.ordinal) // 4 (index, zero-based) }

Built-in Properties và Methods

enum class Season { SPRING, SUMMER, FALL, WINTER } fun main() { val season = Season.SUMMER // Properties println(season.name) // SUMMER println(season.ordinal) // 1 // Get all values Season.values().forEach { println(it) } // Kotlin 1.9+ (recommended) Season.entries.forEach { println(it) } // Parse from String val parsed = Season.valueOf("WINTER") println(parsed) // WINTER // Safe parse (Kotlin 1.9+) val safe = enumValueOf<Season>("WINTER") }
⚠️

valueOf() throws IllegalArgumentException nếu không tìm thấy value. Dùng try-catch hoặc extension function để safe parse.


🔧 Enum với Properties

Mỗi enum constant có thể có properties riêng:

enum class Color(val hex: String, val rgb: Triple<Int, Int, Int>) { RED("#FF0000", Triple(255, 0, 0)), GREEN("#00FF00", Triple(0, 255, 0)), BLUE("#0000FF", Triple(0, 0, 255)), WHITE("#FFFFFF", Triple(255, 255, 255)), BLACK("#000000", Triple(0, 0, 0)); // Computed property val isLight: Boolean get() = rgb.first + rgb.second + rgb.third > 382 } fun main() { val color = Color.RED println("${color.name}: ${color.hex}") // RED: #FF0000 println("RGB: ${color.rgb}") // RGB: (255, 0, 0) println("Is light: ${color.isLight}") // Is light: false // Find by property val whiteColor = Color.entries.find { it.hex == "#FFFFFF" } println(whiteColor) // WHITE }

Ví dụ thực tế: HTTP Status

enum class HttpStatus(val code: Int, val description: String) { OK(200, "Success"), CREATED(201, "Resource created"), BAD_REQUEST(400, "Bad request"), UNAUTHORIZED(401, "Unauthorized"), NOT_FOUND(404, "Not found"), INTERNAL_ERROR(500, "Internal server error"); val isSuccess: Boolean get() = code in 200..299 val isClientError: Boolean get() = code in 400..499 val isServerError: Boolean get() = code in 500..599 companion object { fun fromCode(code: Int): HttpStatus? = entries.find { it.code == code } } } fun main() { val status = HttpStatus.fromCode(404) println(status?.description) // Not found println(status?.isClientError) // true }

🎯 Enum với Abstract Methods

Mỗi constant có thể có implementation khác nhau:

enum class Operation { ADD { override fun apply(a: Int, b: Int) = a + b override val symbol = "+" }, SUBTRACT { override fun apply(a: Int, b: Int) = a - b override val symbol = "-" }, MULTIPLY { override fun apply(a: Int, b: Int) = a * b override val symbol = "*" }, DIVIDE { override fun apply(a: Int, b: Int) = if (b != 0) a / b else throw ArithmeticException() override val symbol = "/" }; abstract fun apply(a: Int, b: Int): Int abstract val symbol: String fun format(a: Int, b: Int): String = "$a $symbol $b = ${apply(a, b)}" } fun main() { println(Operation.ADD.apply(5, 3)) // 8 println(Operation.MULTIPLY.format(5, 3)) // 5 * 3 = 15 Operation.entries.forEach { op -> println(op.format(10, 2)) } // 10 + 2 = 12 // 10 - 2 = 8 // 10 * 2 = 20 // 10 / 2 = 5 }

🔀 Exhaustive when

Khi dùng enum trong when expression, Kotlin đảm bảo bạn phải xử lý tất cả cases:

enum class PaymentStatus { PENDING, PROCESSING, COMPLETED, FAILED, REFUNDED } fun handlePayment(status: PaymentStatus): String = when (status) { PaymentStatus.PENDING -> "Waiting for payment" PaymentStatus.PROCESSING -> "Processing payment..." PaymentStatus.COMPLETED -> "Payment successful" PaymentStatus.FAILED -> "Payment failed" PaymentStatus.REFUNDED -> "Payment refunded" // ✅ Không cần else - compiler biết đã cover hết! } // Nếu add new enum value sau này, compiler sẽ báo lỗi ở mọi when // → Không bao giờ miss case mới!
💡

Best Practice: Tránh dùng else trong when với enum. Nếu add enum value mới, else sẽ silently catch nó thay vì compiler warning bạn update code.


🛠️ Thực hành

Bài tập: Tạo Priority enum

// TODO: Tạo enum Priority với: // - LOW, MEDIUM, HIGH, CRITICAL // - level (Int): 1, 2, 3, 4 // - color (String): hex color // - method isUrgent(): Boolean (HIGH hoặc CRITICAL)

Lời giải:

enum class Priority(val level: Int, val color: String) { LOW(1, "#4CAF50"), // Green MEDIUM(2, "#FFC107"), // Yellow HIGH(3, "#FF9800"), // Orange CRITICAL(4, "#F44336"); // Red fun isUrgent(): Boolean = this in listOf(HIGH, CRITICAL) companion object { fun fromLevel(level: Int): Priority? = entries.find { it.level == level } fun parseOrDefault(name: String, default: Priority = MEDIUM): Priority { return entries.find { it.name.equals(name, ignoreCase = true) } ?: default } } } fun main() { val priority = Priority.HIGH println("${priority.name}: Level ${priority.level}") // HIGH: Level 3 println("Is urgent: ${priority.isUrgent()}") // Is urgent: true println("Color: ${priority.color}") // Color: #FF9800 // Usage val tasks = listOf( "Fix crash" to Priority.CRITICAL, "Update UI" to Priority.LOW, "Performance" to Priority.MEDIUM ) tasks.filter { it.second.isUrgent() } .forEach { println("🔥 Urgent: ${it.first}") } // 🔥 Urgent: Fix crash }

📱 Trong Android

// Network state enum class NetworkState { UNKNOWN, CONNECTED, DISCONNECTED, WIFI, MOBILE } // View visibility enum class Visibility(val value: Int) { VISIBLE(View.VISIBLE), INVISIBLE(View.INVISIBLE), GONE(View.GONE); fun applyTo(view: View) { view.visibility = value } } // Navigation enum class MainScreen(val route: String, val title: String, val icon: Int) { HOME("home", "Home", R.drawable.ic_home), SEARCH("search", "Search", R.drawable.ic_search), PROFILE("profile", "Profile", R.drawable.ic_profile), SETTINGS("settings", "Settings", R.drawable.ic_settings) } // Theme enum class AppTheme { LIGHT, DARK, SYSTEM; fun getMode(): Int = when (this) { LIGHT -> AppCompatDelegate.MODE_NIGHT_NO DARK -> AppCompatDelegate.MODE_NIGHT_YES SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM } }

⚠️ Lưu ý quan trọng

💡

Enum vs Sealed Class:

  • Dùng Enum khi: tất cả values có cùng structure
  • Dùng Sealed Class khi: mỗi subtype cần data khác nhau
// Enum - cùng structure enum class Status { LOADING, SUCCESS, ERROR } // Sealed - khác structure sealed class Result { object Loading : Result() data class Success(val data: List<Item>) : Result() data class Error(val message: String, val code: Int) : Result() }
⚠️

Tránh entries.first { ... } throw exception:

// ❌ Throws NoSuchElementException nếu không tìm thấy val status = HttpStatus.entries.first { it.code == 999 } // ✅ Safe với find val status = HttpStatus.entries.find { it.code == 999 }

✅ Checklist - Tự kiểm tra

Sau bài học này, bạn có thể:

  • Tạo enum class cơ bản
  • Thêm properties cho mỗi enum constant
  • Thêm abstract methods với implementation khác nhau
  • Sử dụng when exhaustive với enum
  • Sử dụng entries để iterate qua enum values
  • Tạo companion object methods (parse, find)
  • Phân biệt khi nào dùng enum vs sealed class

Tiếp theo: Sealed Class

Last updated on