Overview
Sealed classes in Kotlin are used to represent restricted class hierarchies, where a class can have a limited set of subclasses known at compile time. Unlike open classes, which can be extended by any class, sealed classes allow developers to define a closed type system, making it ideal for modeling a fixed set of operations or states in a type-safe manner. They are beneficial for when you need exhaustive when expressions, ensuring you handle all cases without missing any.
Key Concepts
- Restricted Class Hierarchies: Sealed classes provide a way to restrict the hierarchy of classes, ensuring subclasses are defined within the same file.
- Type Safety: By using sealed classes, you prevent the possibility of an invalid subclass, enhancing type safety and predictability in your code.
- Exhaustive When Expressions: Kotlin compiler can ensure that all possible cases are handled in when expressions, reducing the risk of errors.
Common Interview Questions
Basic Level
- What is the purpose of sealed classes in Kotlin?
- How do you declare a sealed class and its subclasses?
Intermediate Level
- How do sealed classes differ from enum classes in Kotlin?
Advanced Level
- Can you provide an example where sealed classes could be used to optimize a design pattern implementation?
Detailed Answers
1. What is the purpose of sealed classes in Kotlin?
Answer: The primary purpose of sealed classes in Kotlin is to control class inheritance. By defining a sealed class, you restrict the creation of subclasses to those explicitly defined within the same file. This approach is particularly useful in situations where a limited number of subclasses are known and managed by the developer, providing a more type-safe way of handling operations like when expressions, where all possible cases can be exhaustively and safely covered.
Key Points:
- Restricts inheritance to a predefined set of subclasses.
- Enhances type safety and predictability.
- Enables exhaustive when expressions for better error handling.
Example:
// Define a sealed class
sealed class UiState
// Define possible states as subclasses
data class Success(val message: String) : UiState()
data class Error(val error: Throwable) : UiState()
object Loading : UiState()
// Use in a when expression
fun handleUiState(uiState: UiState) {
when (uiState) {
is Success -> println(uiState.message)
is Error -> uiState.error.printStackTrace()
is Loading -> println("Loading...")
}
}
2. How do you declare a sealed class and its subclasses?
Answer: A sealed class is declared using the sealed
modifier before the class
keyword. Its subclasses must be declared within the same Kotlin file. Subclasses can either be objects (for stateless singletons) or classes (for stateful instances). This restriction ensures that the set of subclasses is known at compile time, allowing for comprehensive when expressions that cover all possible cases without needing an else
clause.
Key Points:
- Use the sealed
modifier.
- Subclasses must be declared in the same file.
- Supports both object and class subclasses.
Example:
// Declare a sealed class
sealed class Payment
// Subclasses representing different payment methods
data class CreditCard(val number: String, val expiry: String) : Payment()
data class PayPal(val email: String) : Payment()
object Cash : Payment()
// Handling different payment types
fun processPayment(payment: Payment) {
when (payment) {
is CreditCard -> println("Processing credit card payment...")
is PayPal -> println("Processing PayPal payment...")
is Cash -> println("Processing cash payment...")
}
}
3. How do sealed classes differ from enum classes in Kotlin?
Answer: Sealed classes and enum classes both restrict a class to have a limited set of types. However, sealed classes allow each subclass to have its own state and behavior, providing more flexibility than enums. Enum classes are more suitable for simple, fixed sets of constants without the need for complex state or behavior, while sealed classes are better for complex, hierarchical types with varying states and behaviors.
Key Points:
- Sealed classes can have stateful instances with different behaviors.
- Enums are best for simple, fixed sets of constants.
- Sealed classes offer more flexibility and type safety for complex hierarchies.
Example:
// Enum class example
enum class Color {
RED, GREEN, BLUE
}
// Sealed class example with state
sealed class NetworkResponse
data class Success(val data: String) : NetworkResponse()
data class Failure(val error: Throwable) : NetworkResponse()
4. Can you provide an example where sealed classes could be used to optimize a design pattern implementation?
Answer: Sealed classes are particularly useful in implementing the State design pattern in a type-safe manner. By defining all possible states of an object as subclasses of a sealed class, you can ensure that all state transitions are handled explicitly and exhaustively, improving the maintainability and safety of your code.
Key Points:
- Ideal for the State design pattern.
- Ensures type-safe state transitions.
- Allows exhaustive handling of all possible states.
Example:
// Sealed class representing different states of a network request
sealed class NetworkState
object Idle : NetworkState()
object Loading : NetworkState()
data class Loaded(val data: String) : NetworkState()
data class Error(val error: Throwable) : NetworkState()
// Using sealed classes to handle UI state based on network state
fun updateUI(state: NetworkState) {
when (state) {
is Idle -> println("Idle")
is Loading -> println("Loading...")
is Loaded -> println("Data loaded: ${state.data}")
is Error -> println("Error: ${state.error.message}")
}
}
This implementation ensures that all network states are covered, making the code safer and easier to understand.