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
whenexhaustive 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