Sealed Classes vs Enum in Kotlin
Sealed classes and enums are powerful features in Kotlin that help you model restricted class hierarchies and represent a fixed set of values. Let’s explore when and how to use each of them effectively.
Enums
Basic Enum
enum class Direction {
NORTH, SOUTH, EAST, WEST
}
// Usage
val direction = Direction.NORTH
Enum with Properties
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
// Usage
val redRgb = Color.RED.rgb
Enum with Methods
enum class HttpStatus(val code: Int) {
OK(200) {
override fun isSuccess() = true
},
NOT_FOUND(404) {
override fun isSuccess() = false
};
abstract fun isSuccess()
}
Sealed Classes
Basic Sealed Class
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
}
// Usage
val result: Result<String> = Result.Success("Hello")
Sealed Class with Properties
sealed class Shape {
abstract val area: Double
data class Circle(val radius: Double) : Shape() {
override val area: Double = Math.PI * radius * radius
}
data class Rectangle(val width: Double, val height: Double) : Shape() {
override val area: Double = width * height
}
}
When to Use Each
Use Enums When:
// Fixed set of values
enum class DayOfWeek {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
// Simple state representation
enum class ConnectionState {
CONNECTED, DISCONNECTED, CONNECTING
}
// Constants with associated data
enum class HttpMethod(val requiresBody: Boolean) {
GET(false),
POST(true),
PUT(true),
DELETE(false)
}
Use Sealed Classes When:
// Complex state handling
sealed class NetworkResult<out T> {
data class Success<T>(val data: T) : NetworkResult<T>()
data class Error(val exception: Exception) : NetworkResult<Nothing>()
object Loading : NetworkResult<Nothing>()
}
// Expression evaluation
sealed class Expr {
data class Number(val value: Int) : Expr()
data class Sum(val left: Expr, val right: Expr) : Expr()
data class Multiply(val left: Expr, val right: Expr) : Expr()
}
Pattern Matching
With Enums
fun getDirectionName(direction: Direction): String {
return when (direction) {
Direction.NORTH -> "North"
Direction.SOUTH -> "South"
Direction.EAST -> "East"
Direction.WEST -> "West"
}
}
With Sealed Classes
fun evaluate(expr: Expr): Int {
return when (expr) {
is Expr.Number -> expr.value
is Expr.Sum -> evaluate(expr.left) + evaluate(expr.right)
is Expr.Multiply -> evaluate(expr.left) * evaluate(expr.right)
}
}
Best Practices
-
Use enums for simple constants
// Good enum class UserRole { ADMIN, USER, GUEST } // Avoid sealed class UserRole { object Admin : UserRole() object User : UserRole() object Guest : UserRole() }
-
Use sealed classes for complex hierarchies
// Good sealed class Response<out T> { data class Success<T>(val data: T) : Response<T>() data class Error(val message: String) : Response<Nothing>() } // Avoid enum class Response { SUCCESS, ERROR }
-
Leverage exhaustive when expressions
// Good when (result) { is Result.Success -> handleSuccess(result.data) is Result.Error -> handleError(result.message) }
Common Use Cases
API Responses
sealed class ApiResponse<out T> {
data class Success<T>(val data: T) : ApiResponse<T>()
data class Error(val code: Int, val message: String) : ApiResponse<Nothing>()
object Loading : ApiResponse<Nothing>()
}
UI States
sealed class ViewState {
object Loading : ViewState()
data class Content(val data: List<Item>) : ViewState()
data class Error(val message: String) : ViewState()
}
Authentication States
sealed class AuthState {
object Authenticated : AuthState()
object Unauthenticated : AuthState()
data class Error(val message: String) : AuthState()
}
Conclusion
Sealed classes and enums in Kotlin help you:
- Model restricted class hierarchies
- Represent fixed sets of values
- Write type-safe code
- Handle all possible cases
Remember:
- Use enums for simple constants
- Use sealed classes for complex hierarchies
- Leverage pattern matching
- Consider exhaustiveness
Stay tuned for our next post where we’ll explore working with coroutines in Kotlin!